Last time I explained how plain C++ objects and their methods interact with threads. This time around, we will look at QObjects
, and their “thread affinity”.
Now that we know object methods can be accessed by any thread at any time, we’ll consider the situation from the point of view of Qt.
What are QObjects?
Qt is a great framework, and at its heart are
QObjects
.
Through Qt’s moc compiler
extra functionality is seemlessly added to C++ objects.
The two most notable additions are signals and slots for inter-object communication, and events.
These allow great flexibility and modularity, but how do they work in the context of threads?
For example, if an event or slot is triggered in a QObject
(which
ultimately triggers a function), which thread is calling that function? The
answer lies in thread affinity. But let’s back up a bit.
Qt threads and Event loops
Having the ability to asynchronously trigger functions, and raise/handle events
means that Qt must have some kind of an event loop. An event loop will continually
monitor a queue of events to be handled, and dispatch them accordingly.
Indeed, every QThread
has a built-in event loop that can be entered.
One way to see this directly is by inheriting from QThread
:
The above is a good example for demonstration, but is rarely done in production. We will see a better way to run custom code on QThreads in the next section.
In particular the GUI thread (the main thread), also has an event loop which is
launched by calling QApplication::exec()
, which only returns after the user has quit the program.
So far the most important thing to remember is:
Threads in Qt handle asynchronous events, and thus all have an event-loop.
QObjects and QThreads
Now we come to the meat of this post- if C++ objects can be accessed by any
thread, then what thread is handling the events of a particular QObject
?
The answer is that whenever a QObject
is created, it is assigned a parent thread
which handles all of it’s events and slot invocations- it has a thread
affinity. Whichever thread it was created in, becomes it’s parent thread!
This is where the confusion came about for me. On the one hand C++ object
methods can be called from any thread at any time, while QObjects
(themselves
C++ objects) have a parent thread which handles its events. As you can hopefully
see, there is no conflict:
QObject methods can be called from any thread at any time, just like a C++ object. In addition, a parent thread is assigned to handle any asynchronous events and slot invocations.
So there are two ways for a function to be called on a QObject:
- Directly from any thread
- Indirectly by invoking a connected slot or raising an event. This posts an event onto the parent thread’s event loop, which eventually calls the function in question.
To complete this article, let’s look at running our code on other threads.
As promised before we will not inherit from QThread
for the job.
If we can’t customize a thread, and QObjects
are bound to the thread that created them, how can we achieve this? Qt allows users to move QObjects
to other threads, thereby changing the thread affinity to the new thread:
This is much simpler and easier to follow than subclassing a QThread each time you want to create a worker thread. Thanks to Jurily for suggesting this in a reddit comment.
I hope you enjoyed this simplified rundown of QObjects
and threads! More in-depth documentation can be found on the Qt-project website.