Skip to content

Introducing the ConnectionEvaluator in KDBindings Control When Connections are Emitted

Managing the timing and context of signals and slots in multithreaded applications, especially those with a GUI, can be a complex task. The concept of deferred connection evaluation offers a nice and easy API, allowing for controlled and efficient signal-slot connections. This approach is particularly useful when dealing with worker threads and GUI threads.

A classic example where cross-thread signal-slot connections are useful is when we have a worker thread performing some computations or data collection and periodically emits signals. The GUI thread can connect to these signals and then display the data however it wishes. Graphical display usually must happen on the main thread and at specific times. Therefore, controlling exactly when the receiving slots get executed is of critical importance for correctness and performance.

A Quick Recap on Signals & Slots and KDBindings

Signals and slots are integral to event handling in C++ applications. Signals, emitted upon certain events or conditions, trigger connected slots (functions or methods) to respond accordingly. This framework is highly effective, but there are cases where immediate slot invocation is not ideal, such as in multithreaded applications where you might want to emit a signal in one thread and handle it in another, like a GUI event loop.

KDBindings is a headeronly C++17 library that implements the signals and slots design pattern. In addition, KDBindings also provides an easy to use property and bindings system to enable reactive programming in C++. For more information, read this introduction blog.

Understanding Deferred Connection Evaluation

In Qt, a signal that should be evaluated on a different thread will be executed by the event loop of that thread. As KDBindings doesn’t have its own event loop and needs to be able to integrate into any framework, we’re introducing the ConnectionEvaluator as our solution to those nuanced requirements. It allows for a deferred evaluation of connections, providing a much-needed flexibility in handling signal emissions.

In multithreaded environments, the immediate execution of slots in response to signals can lead to complications. Deferred connection evaluation addresses this by delaying the execution of a slot until a developer-determined point. This is achieved through a ConnectionEvaluator, akin to a BindingEvaluator, which governs when a slot is called.

Implementing and Utilizing Deferred Connections

We have introduced connectDeferred. This function, mirroring the standard connect method, takes a ConnectionEvaluator as its first argument, followed by the standard signal and slot parameters.

Signals emitted are queued in the ConnectionEvaluator instead of immediately triggering the slot. Developers can then execute these queued slots at an appropriate time, akin to the evaluateAll method in the BindingEvaluator.

How It Works

1. Create a ConnectionEvaluator:

auto evaluator = std::make_shared<ConnectionEvaluator>(); // Shared ownership for proper lifetime management

2. Connect with Deferred Evaluation:

Signal<int> signal;
int value = 0;

auto connection = signal.connectDeferred(evaluator, [&value](int increment) {
value += increment;
});

3. Queue Deferred Connections

When the signal emits, connected slots aren’t executed immediately but are queued:

signal.emit(5);  // The slot is queued, not executed.

4. Evaluate Deferred Connections

Execute the queued connections at the desired time:

evaluator.evaluateDeferredConnections();  // Executes the queued slot.

Usage Example

To illustrate how this enhanced signal/slot system works in a multithreaded scenario, let’s have a look at a usage example. Consider a scenario where a worker thread emits signals, but we want the connected slots to be executed in an event loop on the GUI thread:

Signal<int> workerSignal;
int guiValue = 0;
auto evaluator = std::make_shared<ConnectionEvaluator>();

// Connect a slot to the workerSignal with deferred evaluation
workerSignal.connectDeferred(evaluator, [&guiValue](int value) {
// This slot will be executed later in the GUI thread's event loop
guiValue += value;
});

// ... Worker thread emits signals ...

// In the GUI thread's event loop or at the right time
evaluator->evaluateDeferredConnections();

// The connected slot is now executed, and guiValue is updated

In this example, the connectDeferred function allows us to queue the connection for deferred execution, and the ConnectionEvaluator determines when it should be invoked. This level of control enables more sophisticated and responsive applications.

Conclusion

In simple terms, the ConnectionEvaluator is a valuable tool we’ve added to KDBindings. It lets you decide when exactly your connections should ‘wake up’ and do their jobs. This is especially helpful in complex applications where you want to make sure that certain tasks are performed at the right time and in the right order (preferably in multithreading applications). Think of it like having a remote control for your connections, allowing you to press ‘play’ whenever you’re ready.

It may also be worthwhile mentioning that we will be integrating this feature into the KDFoundation library and event loop in the KDUtils GitHub repo. Additionally, it’s important to note that it could easily be integrated into any application or framework, regardless of whether it utilizes an event loop.

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

Categories: C++ / KDAB Blogs / KDAB on Qt / Qt

Tags:
Leave a Reply

Your email address will not be published. Required fields are marked *