Qt Signal Slot Mechanism
Signals and slots are loosely coupled: A class which emits a signal neither knows nor cares which slots receive the signal. Qt's signals and slots mechanism ensures that if you connect a signal to a slot, the slot will be called with the signal's parameters at the right time. Signals and slots can take any number of arguments of any type. Signals and slots The Qt framework brings a flexible message exchange mechanism through three concepts: signals, slots, and connections: A signal is a message sent by an object A slot - Selection from Mastering Qt 5 Book.
This blog is part of a series of blogs explaining the internals of signals and slots.
In this article, we will explore the mechanisms powering the Qt queued connections.
Summary from Part 1
In the first part, we saw that signalsare just simple functions, whose body is generated by moc. They are just calling QMetaObject::activate
, with an array of pointers to arguments on the stack.Here is the code of a signal, as generated by moc: (from part 1)
QMetaObject::activate
will then look in internal data structures to find out what are the slots connected to that signal.As seen in part 1, for each slot, the following code will be executed:
So in this blog post we will see what exactly happens in queued_activate
and other parts that were skipped for the BlockingQueuedConnection
Qt Event Loop
A QueuedConnection
will post an event to the event loop to eventually be handled.
When posting an event (in QCoreApplication::postEvent
),the event will be pushed in a per-thread queue(QThreadData::postEventList
).The event queued is protected by a mutex, so there is no race conditions when threadspush events to another thread's event queue.
Once the event has been added to the queue, and if the receiver is living in another thread,we notify the event dispatcher of that thread by calling QAbstractEventDispatcher::wakeUp
.This will wake up the dispatcher if it was sleeping while waiting for more events.If the receiver is in the same thread, the event will be processed later, as the event loop iterates.
The event will be deleted right after being processed in the thread that processes it.
An event posted using a QueuedConnection is a QMetaCallEvent
. When processed, that event will call the slot the same way we call them for direct connections.All the information (slot to call, parameter values, ...) are stored inside the event.
Copying the parameters
The argv
coming from the signal is an array of pointers to the arguments. The problem is that these pointers point to the stack of the signal where the arguments are. Once the signal returns, they will not be valid anymore. So we'll have to copy the parameter values of the function on the heap. In order to do that, we just ask QMetaType. We have seen in the QMetaType article that QMetaType::create
has the ability to copy any type knowing it's QMetaType ID and a pointer to the type.
To know the QMetaType ID of a particular parameter, we will look in the QMetaObject, which contains the name of all the types. We will then be able to look up the particular type in the QMetaType database.
queued_activate
We can now put it all together and read through the code ofqueued_activate, which is called by QMetaObject::activate
to prepare a Qt::QueuedConnection
slot call.The code showed here has been slightly simplified and commented:
Upon reception of this event, QObject::event
will set the sender and call QMetaCallEvent::placeMetaCall
. That later function will dispatch just the same way asQMetaObject::activate
would do it for direct connections, as seen in Part 1
BlockingQueuedConnection
BlockingQueuedConnection
is a mix between DirectConnection
and QueuedConnection
. Like with aDirectConnection
, the arguments can stay on the stack since the stack is on the thread thatis blocked. No need to copy the arguments.Like with a QueuedConnection
, an event is posted to the other thread's event loop. The event also containsa pointer to a QSemaphore
. The thread that delivers the event will release thesemaphore right after the slot has been called. Meanwhile, the thread that called the signal will acquirethe semaphore in order to wait until the event is processed.
It is the destructor of QMetaCallEvent which will release the semaphore. This is good becausethe event will be deleted right after it is delivered (i.e. the slot has been called) but also whenthe event is not delivered (e.g. because the receiving object was deleted).
A BlockingQueuedConnection
can be useful to do thread communication when you want to invoke afunction in another thread and wait for the answer before it is finished. However, it must be donewith care.
The dangers of BlockingQueuedConnection
You must be careful in order to avoid deadlocks.
Obviously, if you connect two objects using BlockingQueuedConnection
living on the same thread,you will deadlock immediately. You are sending an event to the sender's own thread and then are locking thethread waiting for the event to be processed. Since the thread is blocked, the event will never beprocessed and the thread will be blocked forever. Qt detects this at run time and prints a warning,but does not attempt to fix the problem for you.It has been suggested that Qt could then just do a normal DirectConnection
if both objects are inthe same thread. But we choose not to because BlockingQueuedConnection
is something that can only beused if you know what you are doing: You must know from which thread to what other thread theevent will be sent.
The real danger is that you must keep your design such that if in your application, you do aBlockingQueuedConnection
from thread A to thread B, thread B must never wait for thread A, or you willhave a deadlock again.
When emitting the signal or calling QMetaObject::invokeMethod()
, you must not have any mutex lockedthat thread B might also try locking.
A problem will typically appear when you need to terminate a thread using a BlockingQueuedConnection
, for example in thispseudo code:
You cannot just call wait here because the child thread might have already emitted, or is about to emitthe signal that will wait for the parent thread, which won't go back to its event loop. All the thread cleanup information transfer must only happen withevents posted between threads, without using wait()
. A better way to do it would be:
The downside is that MyOperation::cleanup()
is now called asynchronously, which may complicate the design.
Conclusion
This article should conclude the series. I hope these articles have demystified signals and slots,and that knowing a bit how this works under the hood will help you make better use of them in yourapplications.
last modified December 22, 2020
In this part of the Qt5 C++ programming tutorial we talk about events andsignals.
Events are an important part in any GUI program. All GUI applications areevent-driven. An application reacts to different event types which are generatedduring its life. Events are generated mainly by the user of an application. Butthey can be generated by other means as well, e.g. Internet connection, windowmanager, or a timer. In the event model, there are three participants:
- event source
- event object
- event target
The event source is the object whose state changes. It generatesEvents. The event object (Event) encapsulates the state changes in theevent source. The event target is the object that wants to be notified.Event source object delegates the task of handling an event to the event target.
When we call the application's exec
method, the application entersthe main loop. The main loop fetches events and sends them to the objects. Qthas a unique signal and slot mechanism. This signal and slot mechanism is anextension to the C++ programming language.
Signals and slots are used for communication between objects. A signalis emitted when a particular event occurs. A slot is a normal C++method; it is called when a signal connected to it is emitted.
Qt5 click example
The first example shows a very simple event handling example. We have one pushbutton. By clicking on the push button, we terminate the application.
Qt Signal Slot Example
This is the header file.
We display a QPushButton
on the window.
The connect
method connects a signal to the slot. When we click onthe Quit button, the clicked
signal is generated. TheqApp
is a global pointer to the application object. It isdefined in the <QApplication>
header file. Thequit
method is called when the clicked signal is emitted.
This is the main file.
Qt5 KeyPress
In the following example, we react to a key press.
This is the keypress.h
header file.
The application terminates if we press the Escape key.
One of the ways of working with events in Qt5 is to reimplement an event handler.The QKeyEvent
is an event object, which holds information aboutwhat has happened. In our case, we use the event object to determine which key wasactually pressed.
This is the main file.
QMoveEvent
Qt Signal Slot Thread
The QMoveEvent
class contains event parameters for move events.Move events are sent to widgets that have been moved.
This is the move.h
header file.
In our code programming example, we react to a move event. We determinethe current x, y coordinates of the upper left corner of the client areaof the window and set those values to the title of the window.
We use the QMoveEvent
object to determine the x
,y
values.
We convert the integer values to strings.
The setWindowTitle()
method sets the text to the titleof the window.
This is the main file.
Qt Signal Slot Mechanism Lock
Disconnecting a signal
A signal can be disconnected from the slot. The next example shows howwe can accomplish this.
In the header file, we have declared two slots. The slots
is not a C++ keyword, it is a Qt5 extension.These extensions are handled by the preprocessor, before the code is compiled.When we use signals and slots in our classes, we must providea Q_OBJECT
macro at the beginning of theclass definition. Otherwise, the preprocessor would complain.
In our example, we have a button and a check box. The check box connectsand disconnects a slot from the buttons clicked signal. This example mustbe executed from the command line.
Here we connect signals to our user defined slots.
If we click on the Click button, we send the 'Button clicked' text tothe terminal window.
Inside the onCheck()
slot, we connect or disconnect theonClick()
slot from the Click button, depending on the received state.
This is the main file.
Timer
A timer is used to implement single shot or repetitive tasks. A good examplewhere we use a timer is a clock; each second we must update our label displayingthe current time.
This is the header file.
In our example, we display a current local time on the window.
To display a time, we use a label widget.
Here we determine the current local time. We set it to the label widget.
We start the timer. Every 1000 ms a timer event is generated.
To work with timer events, we must reimplement the timerEvent()
method.
This is the main file.
This chapter was dedicated to events and signals in Qt5.