Effective Threading Using Qt
Over the years using Qt I’ve seen a lot of difficulty using threads with Qt.Threads can be difficult and Qt provides a lot of ways to make threads easy towork with. Still basic / direct / low level threading (I’ll just call thisbasic) is often seen as difficult with Qt. It really isn’t though.
There are three main ways I’ve seen people handle basic threading in their Qtapplications. The first is using system threads, either pthread or Windowsthreads. I don’t like this approach because you’re basically writing a portablethread library. You can use an already built portable thread library but whyuse a non-Qt solution when Qt already provides portable threading.
The two other approaches are defined by Qt’s QThreaddocumentation. 1) use a QObject worker. 2)subclass QThread and reimplement the run function.
I like using a QObject worker and I think this is the best (and easiest)approach. That said, I don’t like how Qt’s documentation explains this. It usestwo QObject classes to handle this. A variation of this approach is what I’mgoing to demonstrate.
The other documented approach is subclassing QThread. This isn’t a horribleidea but I don’t think it’s the cleanest approach. It also ties thefunctionality to a thread making code reuse low in this case. You could getaround this limitation by putting the functionality in a separate class but nowyou have two classes and the thread subclass really isn’t necessary when usingthe QObject worker approach.
This paper introduces the sigslot library, which implements a type-safe, thread-safe signal/slot mech-anism in C. The library is implemented entirely in C, and does not require source code to be pre-processed1 in order for itto be used.
This is a very simple example that demonstrates two types of workers. One takesarguments and runs until it’s task is finished. The second on runs until it’stold to stop. The second worker could take arguments if you need it to. Forthis example the workers both just increment a count every second and send theresult to a
QMainWindow for it to be displayed.
Here is all the code that goes into the example which will have the main partsexplained after:
- I have a Qt 'signal-slot connection'. I want to test the class with the signal The class with the slot is mocked/stubbed. Now I would like to verify that when I emit signal, the slot is called. I wonder if it is possible to use gmock to verify this. // class Tx contains the signal, which is protected so I inherit // to get access to it.
- A long running operation has to be put in another thread. During the course of processing, status information should be sent to the GUI thread. Use QThread, reimplement run and emit signals as needed. Connect the signals to the GUI thread's slots using queued signal/slot connections. One call: Operations are to be performed on all items of a.
This is a very simple object that increases a count every second. The start andend are set as part of the constructor. Setting up the object using theconstructor makes sense but it’s also because to start the worker we can’t passany arguments. This is a limitation of this method that the worker in Qt’s docsdoes not have. That said, I think it’s fine to use the constructor for argumentpassing. This method doesn’t reuse workers (not a limitation) so it’s fine.Once the worker finishes it’s work is all done and it stops.
This worker is very similar to the count worker. It’s to demonstrate infinitetasks unlike CountWorker which demonstrates finite tasks.
The key to his worker is the stopWork slot. Calling this sets sets a flag tolet the worker’s doWork function to exit.
Both workers have two signals in common,
UpdateCount simply sends the current count off. The
MainWindow connects anduses this to, well, update the count. finished signals that the worker is done.This is used to stop the thread and takes care of cleanup. The finished signalcan have multiple overloads. For example to send off a result in addition tosignaling that the worker is finished. You must have a no argument signal forcompletion with this method. You could use a different signal name instead ofoverloading “finished” if you want.
One thing to think about is thread synchronization. Well, with this workermethod you don’t need to worry about it. Initialization of parameters happensbefore the worker is moved to the thread and before the thread is even started.All passing (such as updateCount) happens using signals and slots. When passingdata between threads using signals and slots Qt handles thread synchronizationfor you. The
stopWork function is called via a signal so the function runs onthe thread the work is running on between iterations of the while loop. Sothere is no need to wrap
m_running in a mutex or other synchronizationtechniques.
It might be confusing that I said the
stopWork function happens betweeniterations of the while loop in InfiniteCountWorker. This is because of the
qApp->processEvents call. Without this, the signal to initiate the
stopWorkslot won’t be delivered until after the while loop finishes. Which isimpossible in
infiniteCountWorker. Remember the thread is a single thread andeverything running on it is single threaded. The while loop will block anythingelse in the object from running unless you use
qApp->processEvents to allowsignals and slots to process. In the case of the
qApp->processEvents isn’t really necessary since there aren’t any signalsthat are delivered to it.
processEvents is only necessary for signals (fromoutside of or within the thread the worker is running) to initiate slots in theworker. It is not necessary for the worker to send off a signal (like
updateCount) to another thread that it’s connected to.
If you haven’t realized by now that with the worker method (my variation orQt’s) the worker has an event loop. Subclassing
QThread only does in somecases (see the documentation for when). You get signals into and out of workerwithout any additional code (except for a single
processEvents call). You getthis essentially for free by using a worker!
The workers aren’t really useful unless they can be started on a thread. That’swhat the
MainWindow in this example is for. The
startInfiniteCount functions are the main thing to look at.
The thread and worker are created on the heap. The worker is initialized heretoo.
Put the worker on the thread so the worker will be run on the thread we createdinstead of the UI thread.
This is how the worker is started when the thread is started.
Connect all the finished signals. When the worker finishes the thread will bestopped with
quit(), the worker will be deleted via
deleteLater. Qt handlesdeletion and takes care of it when safe. This is another advantage of using
QObject workers. Also, when the worker finishes the
countFinished functionis called so anything in the
MainWindow that needs to happen when the workeris finished is run. Again, you could have a second finished signal (overload)that passes result data back to the
MainWindow. Finally, when the threadfinishes (because of the worker’s finished signal calling the threads quitslot) it will be deleted by
deleteLater slots mean wedon’t need to track the the worker or thread pointers and worry about manuallydeleting them.
deleteLater is very useful. We have finished connected to multiple slots.Deleting a
QObject when there are events pending or within a signal handler(slot) can lead to a crash.
deleteLater ensures that this won’t happen bywaiting until all events are delivered. Also, you can’t delete a
QObject froma thread so this also ensures the UI thread that created the worker and threadis where they are deleted. In this example this isn’t a concern but it’s niceto know that it won’t become one.
We don’t need to worry about something like dangling connections because when a
QObject is deleted it automatically disconnects all signals and slots (not inall cases but that will be covered later).
Here the count will be updated on the
MainWindow as it’s incremented in theworker. Again, there is no manual thread synchronization necessary because Qthandles this as part of it’s signal and slot system.
The last line to worry about actually starts the thread which in turns startsthe worker.
This function is very similar to
startCount. The only difference is one line.
MainWindows button that will stop the
InfiniteCountWorker is connectedto the
InfiniteCountWorker’s stop function.
portablesleep.h is a header only cross platform sleep class. This is how wecreate the one second delay when counting. With Qt5 you can (should) use
sleep public static functions instead. Qt4 on theother hand defines these functions as protected static functions. So there isno clean way to use them aside from creating a
QThread subclass and exposingthem. Note that this example is not limited to Qt4. This example will runand work with both Qt4 and Qt5. A little bit of thought is all that’s needed toachieve this.
This information isn’t specific to threading but could be very useful to reallyget the most of the worker concept.
This example only uses int. This is a type that can be used with Qt’s signalsand slots as well as many other types such as
QString. There are many complextypes like your own classes or even
QMap<QString, QString>> cannot be usedimmediately with signals and slots.
I say immediately because any class with a public constructor, copy constructorand destructor can be registered with Qt using
qRegisterMetaType. This allowsthe object type to be used with signals and slots. For example you can use thefollowing in the
MainWindow’s constructor to allow a complex type to be used.
The reason we need constructor, copy constructor and destructor is because whennecessary Qt will create a copy of an object and pass the copy to a slot. Inthe case of passing objects between threads using signals and slots a copy willbe passed to the slot. Remember primitive types like int and pointers arealways copies. Complex types may or may not be copies depending on thesituation.Realize that if you are passing a pointer between threads you will need manualsynchronization. If you’re allowing copies to be passed then you don’t need toworry about synchronization.
You’ve probably noticed that in this example the old style
SLOTmacros were used in the connect functions. This is on purpose because itsupports more compilers. Also, as mentioned, the example code works with bothQt4 (it is still used) and Qt5. I don’t really like new syntax provided byC++11 either. There are too manynegatives for my liking. In thiscase the fact that automatic connection disconnection isn’t support is prettybig. Nor is overloading signals very clean which makes using multiple versionof finish not a friendly. But this is my personal preference and not requiredfor this threading method.
Contrary to popular belief Qt provides very powerful basic threadingcapabilities. I say basic because I didn’t even mention Qt Concurrent whichprovides a lot of high level threading support. Oh and
QThread pool wasn’tmentioned. None of the thread synchronization objects like
QMutex weredescribed either.
Even with all of the threading objects that Qt provides it’s still really easyto use threading with
QObject workers. For most people this is going to beenough for offload some functionality and keep the UI from blocking. While thisis the method I use quite often there are plenty of other ways. I just happento find this to be the cleanest and easiest.
Some times you see a signal is emitted in sender thread but connected slot doesn't called (in other words it doesn't receive signal), you have asked about it and finaly got that the connection type Qt::DirectConnection would fix it, so the problem found and everything is ok.
But generaly this is bad idea to use Qt:DirectConnection until you really know what is this and there is no other way. Lets explain it more, Each thread created by Qt (including main thread and new threads created by QThread) have Event loop, the event loop is responsible for receiving signals and call aproporiate slots in its thread. Generaly executing a blocking operation inside an slot is bad practice, because it blocks the event loop of that threads so no other slots would be called.
Qt Thread Signal Slot
If you block an event loop (by making very time consuming or blocking operation) you will not receive events on that thread until the event loop will be unblocked. If the blocking operation, blocks the event loop forever (such as busy while), the slots could never be called.
In this situation you may set the connection type in connect to Qt::DirectConnection, now the slots will be called even the event loop is blocked. so how this could make broke everything? In Qt::DirectConnection Slots will be called in emiter threads, and not receiver threads and it can broke data synchronizations and ran into other problems. So never use Qt::DirectConnection unless you know what are you doing. If your problem will be solved by using Qt::DirectConnection, you have to carefull and look at your code and finding out why your event loop is blocked. Its not a good idea to block the event loop and its not recomended in Qt.
Here is small example which shows the problem, as you can see the nonBlockingSlot would be called even the blockingSlot blocked event loop with while(1) which indicates bad coding