4/8/2022»»Friday

Qthread Slot Connection

4/8/2022
Qthread Slot Connection 5,1/10 3952 reviews

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 areQObjects.Through Qt’s moc compilerextra 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 (whichultimately triggers a function), which thread is calling that function? Theanswer 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 eventsmeans that Qt must have some kind of an event loop. An event loop will continuallymonitor 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 QSlotObject is a wrapper around the slot that will help calling it. It also knows the type of the signal arguments so it can do the proper type conversion. We use ListLeft to only pass the same number as argument as the slot, which allows connecting a signal with many arguments to a slot with less arguments. Example for QThread without inheriting from QThread directly, but changing thread affinity of QObject via obj-moveToThread(thread). New QT Connection syntax. This is a example how to add QObject to an QThread via MoveToThread. The goal is to achieve a constantly updating list, which will then be processed.

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.

Cross-thread signal-slot connections are implemented by dispatching a QMetaCallEvent to the target object. A QObject instance can be moved to a thread, where it will process its events, such as timer events or slot/method calls. To do work on a thread, first create your own worker class that derives from QObject. Then move it to the thread. Its important to use a direct connection here // because moveToThread must be called in the GraphicsThread (finished is emitted in the GraphicsThread context) connect ( qThread, SIGNAL ( finished ( ) ), this, SLOT ( moveBack ( ) ), Qt:: DirectConnection ).

In particular the GUI thread (the main thread), also has an event loop which islaunched 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.

Qthread Slot Connection

QObjects and QThreads

Now we come to the meat of this post- if C++ objects can be accessed by anythread, 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 threadwhich handles all of it’s events and slot invocations- it has a threadaffinity. Whichever thread it was created in, becomes it’s parent thread!

Slot

This is where the confusion came about for me. On the one hand C++ objectmethods can be called from any thread at any time, while QObjects (themselvesC++ objects) have a parent thread which handles its events. As you can hopefullysee, 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 anyasynchronous 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 moveQObjects to other threads, thereby changing the thread affinity to the new thread:

Connection

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.

There are basically two different ways of using QThread directly: Worker threads or subclassing. Those two are confusing to a lot of developers, and I will try to explain when you should use those. I will also explain how signals and slots work with threads and how they can help you or lead to problems.

There are other ways to use QThread than those two. There are hybrid cases that lie somewhere between the two general classes, and you can use QThreadPool or the QtConcurrent. Those cases will not be described here, but I might come back to those in later blog posts.

When you look at the discussion on whether or not to subclass QThread, you will find a lot of people who claim this is bad. For example the famous “You’re doing it wrong” blog post from 2010 (yes, this is an old discussion). I have mixed feelings about this blog post and the arguments it presents. On one hand, I actually encourage developers to follow the recommendations in this article, because I agree that this is the path that leads to fewer errors in your code. OTOH, there are certainly valid use cases for subclassing.

The fundamental problem here is that most developers do not really understand the thread model of Qt. And in some cases they do not even understand threading at all. There are a lot of developers in this industry who should not touch multithreaded code at all, but still have tasks in their daily job that force them to do this. I won’t try to educate general thread development here, but I will encourage everyone who needs to learn this to read a good book on multithreaded development. I’m only going to talk about the QThread class here and assume you understand threading.

So let’s get started on the issue at hand.

There are two Qt technologies that you need to understand before you can understand why QThread works the way it does.

QObject thread affinity

The first of those two technologies is thread affinity. Each QObject has a thread that it belongs to. Unless you set this manually, QObject chooses the current thread when the constructor is run.

Thread affinity has a bunch of subtle consequences for our objects, once you start going multithreaded. Of course, if you don’t have any threads, the objects all live in the main thread, and then you don’t have to worry about it. But with threading, you do have to worry about it.

The first thing you have to know is what this means for signals and slots.

When you do a connect from a signal to a slot, you have a choice of connection type. Those can be automatic, direct or queued. Yes, there are a couple of others, but those are not interesting from a threading point of view.

With a direct connection, the slot will be called by the current thread that is emitting the signal. Usually, this is very bad, if the two objects live in different threads. There are cases where you can do this – one example could be a slot that locks a mutex, adds something to a queue, unlocks and returns. But you have a problem if the object can be deleted by another thread while your slot is being executed, of course. My recommendation is not to use this unless you absolutely have to, you really know what you are doing, and make sure you document both sides very precisely.

For queued connections to work, the thread where the receiving object lives (i.e. has the thread affinity), must have an active event loop. This is not necessary for the sender. What happens is that the sending signal will create an event with the arguments of the signal and send this event to the receiver thread eventloop. In the eventloop, the event is transformed to a normal slot call. This happens no matter if the two objects live in the same thread or if they are in different threads. (And this is one of the simplest ways to send information from one thread to another.)

Automatic connection does a check in the signal whether the thread affinity of the receiver is the same as the current thread. If these are the same, it does a direct signal to slot call. If they are different, this is handled as a queued connection. This check is done every time the signal is emitted, which means the thread of the signal is irrelevant and the connect just works. Note that the signalling object does not have to live in a thread with an event loop.

QThread thread affinity

The second thing you need to understand, is the thread affinity of the QThread object. This is the part that confuses a lot of developers, although the rules are actually quite simple.

The thread affinity of a QThread object is the creating thread. It does not live in the thread that it models and implements. This may sound counterintuitive, until you actually think about this.

First, when you create the QThread object, the new thread doesn’t exist. This means the constructor will necessarily have to run in the thread that creates it. Also, when the thread is finished and you want to delete it, this will be done in the creating thread as well, so the destructor runs in the creating thread.

However, in the run method of QThread, you are now in the new thread.

This means when you create objects in a QThread subclass method, they will have different thread affinity based on the instantiation time. If you create an object in the constructor (or in the destructor, but that’s rare) this object will have the calling thread affinity. If you create an object in the run method, this will have the new thread affinity. If you have a method on the QThread subclass, an object created in here will (as usual) have the current thread affinity – so if you call it from the constructor it will have the creating thread affinity, and if you call it from run it will have the new thread affinity.

For signals, the thread affinity doesn’t matter. But for slots it matters a lot.

Subclassing QThread

Now you can perhaps appreciate why subclassing QThread can be a problem. If you have anything using slots in the subclass objects, the thread affinity will mean that it will use the original thread. This is also the case for lambda slots or what other tricks you could come up with.

So, to “fix” this, developers try various workarounds. First, they use moveToThread(this) on the thread object. Unfortunately, this is probably the worst thing you can do, because that is a violation of the basic assumption of QThread itself. Others will connect using the DirectConnection flag, which actually works, but is a very brittle solution that probably will break while people are working on the code in the years to come.

The next problem you have to consider is when you instantiate other objects from the thread object itself. If you want an object to live in the new thread, you must instantiate it inside the run method and not set the thread object as the parent, because Qt does not like the parent-child relationship to go across from one thread to another.

Recommendations

So, now we’re finally ready for a list of what you should do in different cases.

If you have a task you need to run in a QThread, this is the place where you do as the QThread documentation says, and create a worker thread. Just instantiate a QThread object, instantiate the objects to live in this thread, call moveToThread(thread) on those objects and start the thread. This is what you should do for most cases.

If you have something that’s just a pure calculation or something that connects to hardware, then you will often use a QThread subclass. Yes, you can implement this using worker threads as well, but that’s usually a silly way to do it. (For the pure calculation case, I would actually use QtConcurrent or the QThreadPool, but that’s a topic for a later blog post.)

When you look at this recommendation, there are two things that are the defining questions:

  • Do you need slots?
  • Do you need QObject instances in the new thread?

Qthread Slot

If the answer to one of those questions is yes, then you should follow the standard recommendation and use a worker thread. If the answer is no, you have a choice between subclassing and worker threads.

Qthread Lock

If you answer yes to either of those questions and still choose subclassing, you are probably going to have issues that are really hard to find. Remember, there’s actually a reason people suggest that you don’t subclass QThread. If you do it, you expose yourself to a couple of sets of problems that you don’t have when you use a worker thread.