A Qt C++ alternative for handling async code execution, specially useful for threaded applications.
This library consists of two main helper classes:
-
QLambdaThreadWorker
: execute code in a thread. -
QDeferred
: pass data between threads.
Qt already provides a work flow for safely execute code in threads which can be found in their documentation, but it basically consists in:
-
Create a "worker" object which implements the code to be executed in a thread in Qt slots.
-
Use Qt signals within the "worker" object to notify when the work is done.
-
Create a "controller" object that owns an instance of the "worker" object and an instance of a
QThread
. -
Upon "controller" object instantiation move the "worker" object to the
QThread
instance and start the thread. -
Use Qt signals within the "controller" object to invoke the Qt slots in the "worker" object (which actually do the threaded work).
-
Use Qt slots within the "controller" object to handle the results of the threaded work.
-
Do to forget to quit the thread and wait for it in the "controller" object destructor.
I think this already explains the why, not only do we have to create two classes, but we have to remeber to implement all this signals and slots, connect them, move objects to threads, etc. And I almost forgot; is not like you can send any type using signals and slots, if you want so send a custom type you have to do a lot of stuff to make your type Qt-compatible.
Qt provides other classes that facilitate threaded and retrieval of results of async code execution, namely QtConcurrent
, QFuture
and QFutureWatcher
. A comparison between QtConcurrent
and QDeferred
async code and their differences is shown in the last section of this document.
QDeferred
is not the one and only solution for your threaded code execution problems, but just another tool that can help create nice async APIs, easy to read and write code. See the Conclusions and Recommendations section at the end of this document for recommendations on when to use QDeferred
.
To include this library in your project, just include qlambdathreadworker.pri and qdeferred.pri in your QMake Project file (*.pro
file) as follows:
include($$PATH_TO_THIS_REPO/src/qdeferred.pri)
include($$PATH_TO_THIS_REPO/src/qlambdathreadworker.pri)
Just replace $$PATH_TO_THIS_REPO
with the relative or absolute path to this repo. Then in your C++ code include the headers:
#include <QLambdaThreadWorker>
#include <QDeferred>
Take a look at the tests folder to see how projects are created.
This library requires C++11.
This class is the first of two parts of the proposed solution. To execute code in a thread we use QLambdaThreadWorker
as follows:
#include <QCoreApplication>
#include <QDebug>
#include <QLambdaThreadWorker>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QLambdaThreadWorker worker;
worker.execInThread([]() {
qDebug() << "Hello world from worker thread" << QThread::currentThread();
});
qDebug() << "Hello world from main thread" << QThread::currentThread();
return a.exec();
}
Possibe output:
Hello world from main thread QThread(0x1a1398cae10)
Hello world from worker thread QThread(0x1a1398cdc50)
There is no more to it, just give the execInThread
method a lambda, and it will execute it in a thread.
The QLambdaThreadWorker
handles just one thread which is reusable, let's call it thread A. This means that every time we call the execInThread
method on the same QLambdaThreadWorker
instance, we are exectuing code in the same thread A.
If we make a second QLambdaThreadWorker
instance, all the calls to the execInThread
method, will be executed in thread B. You get the idea.
Internally, QLambdaThreadWorker
uses Qt's event loop, so all calls to the execInThread
method are serialized in the order they were called. So all is safe and good Qt-wise.
Now you might be wondering : "Ok cool, but how to I pass data and retrieve data from the thread?". I am glad you asked!
To pass data data to the thread is easy, just pass it through the lambda capture:
QLambdaThreadWorker worker;
QString strName = "Laura";
// pass 'strName' through lambda capture by copy
worker.execInThread([strName]() {
qDebug() << "Hello" << strName;
});
In the previous snippet we passed the string by-copy. We could also pass variables by-reference, but we would have to be careful that the variables still exist by the time the lambda is executed.
Note that we can pass any type though the lambda capture. There is no need to register our custom types in the Qt's system.
Now, to retrieve data from the thread we use the second part of the solution, the QDeferred
class.
Note : similar results can be achieved with QtConcurrent
:
QFuture<void> future = QtConcurrent::run([=]() {
// Code in this block will run in another thread
});
See the end of this document to see what advantages QDeferred
provides.
The QDeferred
represents the result of some async code execution, it also represents whether the execution of the code suceeded or failed.
The QDeferred
API is divided in two:
-
The privider API : the methods used by the provider (or producer) of the result.
-
The consumer API : the methods used by the consumer of the result.
The way it works is that the provider, which might be just a simple function, returns a templated QDeferred
instance to the consumer.
For example, if the result is an integer, the function signature would be as follows:
QDeferred<int> multiplyPositiveNumbers(int x, int y);
Note that we define the actual return type in the template argument <int>
. We could put there any other type, without the need to register it in the Qt's system.
The actual implementation of that function, using the provider API would like something like this:
QDeferred<int> multiplyPositiveNumbers(int x, int y)
{
QDeferred<int> retDefered;
if (x < 0 || y < 0)
{
retDefered.reject(0);
}
else
{
retDefered.resolve(x * y);
}
return retDefered;
}
We use the reject
method to indicate that the execution has failed, and we use the resolve
method to indicate that the execution has succeded. As arguments to these methods we pass in the results.
Bear in mind that the arguments to the reject
and resolve
methods must match the template argument provided to the QDeferred
object, else the compiler will complain.
Now we can use the consumer API to handle the result of calling the multiplyPositiveNumbers
method. It would be as follows:
multiplyPositiveNumbers(3, 4)
.fail([](int res) {
qDebug() << "multiplyPositiveNumbers failed!";
})
.done([](int res) {
qDebug() << "multiplyPositiveNumbers succeded!";
});
The multiplyPositiveNumbers
return the QDeferred<int>
instance, so we can inmediatly call the fail
method on it. We pass a lambda to the fail
method which acts as a callback in case the execution failed.
The fail
method itself returns the same QDeferred<int>
instance which allows to call the done
method inmediatly. The lambda passed to the done
method acts as a callback in case the execution succeded.
The fact that both the fail
and done
methods return the same QDeferred<T>
instance, allow us to chain the methods and call them in a row as many times as we want, which is some nice syntactic sugar to have.
In fact we can call the fail
and done
methods as many times as we want in a row:
multiplyPositiveNumbers(3, 4)
.done([](int res) {
qDebug() << "multiplyPositiveNumbers succeded!";
})
.done([](int res) {
qDebug() << 3 << "*" << 4 << "=" << res;
})
.done([](int res) {
qDebug() << "Hell yeaaaahh!";
});
The output would be:
multiplyPositiveNumbers succeded!
3 * 4 = 12
Hell yeaaaahh!
If we change the first argument of multiplyPositiveNumbers
to be a negative number:
multiplyPositiveNumbers(-3, 4)
.fail([](int res) {
qDebug() << "multiplyPositiveNumbers failed!";
})
.done([](int res) {
qDebug() << "multiplyPositiveNumbers succeded!";
});
The output will be:
multiplyPositiveNumbers failed!
Note that the callbacks passed in to the fail
and done
methods must be lambdas whose arguments match the types passed in the template argument of the QDeferred
. For example if we create a new deferred object like:
QDeferred<int, QString> retDefered;
Then the reject
and resolve
methods would need to be called with the correct types <int, QString>
like:
retDefered.resolve(3, "Hello");
And the fail
and done
lambdas should also have <int, QString>
as arguments:
retDefered.done([](int num, QString string){
// do something with the <int, QString> results
});
We can pass as many template arguments as we want to QDeferred<...>
, potentially having any number of return values.
So far we have seen no advantage in using QDeferred<T>
as a return argument of a function, it is actually much more cumbersome than just returning the resulting integer directly. The real value comes when using QDeferred<T>
as return values of threaded code execution.
Let's assume I want to execute the multiplyPositiveNumbers
in a thread. Using our QLambdaThreadWorker
helper, we could implement it as follows:
QLambdaThreadWorker worker;
QDeferred<int> multiplyPositiveNumbers(int x, int y)
{
QDeferred<int> retDefered;
worker.execInThread([retDefered, x, y]() mutable {
if (x < 0 || y < 0)
{
retDefered.reject(0);
}
else
{
retDefered.resolve(x * y);
}
});
return retDefered;
}
There are two important things to realize from the previous code:
-
The first one is that the
QLambdaThreadWorker
is outside the function, not in the stack. The reason is because if we put it on the stack, theQLambdaThreadWorker
would disappear as soon as the function returns, without the chance to execute the threaded code. -
The second is the use of the
mutable
keyword in the lambda definition. This is because the lambda actually changes theQDeferred<int>
state when calling thereject
orresolved
methods.
The consumer of this function would use it as follows:
int x = 3;
int y = 4;
multiplyPositiveNumbers(x, y)
.fail([](int res) {
Q_UNUSED(res);
qDebug() << "multiplyPositiveNumbers failed!";
})
.done([x, y](int res) {
qDebug() << x << "*" << y << "=" << res;
});
The fail
and done
callbacks are actually executed in the same thread where the multiplyPositiveNumbers
was called, while the actual multiplication code is executed in the thread handled by QLambdaThreadWorker
.
The QDeferred<T>
instance makes sure that the callbacks are executed in the thread where they were defined, regardless of the thread where the reject
or resolve
methods are called. This is all automagically handled behind the scenes using the Qt's event loop.
So that's it! You managed to execute code in a thread, pass in data and returning the result safely.
All of this without creating any class definitions, or connecting signals, or registering types. Actually all the code fits nicely in the main
method. Less than 50 lines of code in total, including error handling.
#include <QCoreApplication>
#include <QDebug>
#include <QLambdaThreadWorker>
#include <QDeferred>
QLambdaThreadWorker worker;
QDeferred<int> multiplyPositiveNumbers(int x, int y)
{
QDeferred<int> retDefered;
worker.execInThread([retDefered, x, y]() mutable {
qDebug() << "Execution in thread :" << QThread::currentThread();
if (x < 0 || y < 0)
{
retDefered.reject(0);
}
else
{
retDefered.resolve(x * y);
}
});
return retDefered;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int x = 3;
int y = 4;
qDebug() << "Call in thread :" << QThread::currentThread();
multiplyPositiveNumbers(x, y)
.fail([](int res) {
Q_UNUSED(res);
qDebug() << "multiplyPositiveNumbers failed!";
})
.done([x, y](int res) {
qDebug() << "Result in thread :" << QThread::currentThread();
qDebug() << x << "*" << y << "=" << res;
});
return a.exec();
}
The output is:
Call in thread : QThread(0x178e39ba640)
Execution in thread : QThread(0x178e39baaf0)
Result in thread : QThread(0x178e39ba640)
3 * 4 = 12
You thought that was it? That was just a simple use case. QLambdaThreadWorker
and QDeferred
still have some very useful functionalities that will make your life easier when coding threaded applications.
For example, another interesting thing to do in a thread is to execute code in a cycle. With QLambdaThreadWorker
you can do exactly this, by calling the startLoopInThread
method:
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int counter = 0;
QLambdaThreadWorker worker;
worker.startLoopInThread([&counter]() {
qDebug() << "Counting" << counter++;
}, 1000);
return a.exec();
}
The previous example will count in a thread every second indefinetively.
We pass as a first argument to the startLoopInThread
method, a lambda to get executed cyclically in the thread. As a second argument we pass the cycle time in miliseconds.
Note that in the previous example, the counting works because the counter
variable is passed in by-reference, so the counter++
actually acts on the same variable. We managed to get away with it because we know that a.exec()
is blocking, thus the counter
variable exists as long as the QCoreApplication
exists.
The startLoopInThread
method returns an int
that works as a cycle handler. We can use this handler to stop the cycle later using the stopLoopInThread
method:
int counter = 0;
QLambdaThreadWorker worker;
int handle = worker.startLoopInThread([&counter]() {
qDebug() << "Counting" << counter++;
}, 1000);
QTimer::singleShot(5000, [worker, handle]() mutable {
worker.stopLoopInThread(handle);
});
The previous example will count in a thread from 0 to 4 and then stop. Note the need of the mutable
keyword in the QTimer::singleShot
callback, because the stopLoopInThread
modifies the QLambdaThreadWorker
state.
One important fact to underscore, is that even after calling the startLoopInThread
method, the QLambdaThreadWorker
is still reusable. Meaning we can still call the execInThread
while a cyle is running, and it will work:
int counter = 0;
QLambdaThreadWorker worker;
worker.startLoopInThread([&counter]() {
qDebug() << "Counting" << counter++;
}, 1000);
QTimer::singleShot(5000, [worker]() mutable {
worker.execInThread([]() {
qDebug() << "It has beed 5 seconds.";
});
});
The possible output:
Counting 1
Counting 2
Counting 3
Counting 4
It has beed 5 seconds.
Counting 5
Counting 6
Both callbacks are being executed in the same thread. This is possible because a cycle in the thread leaves free time within executions, so we can use the same thread to do many other things on that free time.
The executions will never overlap because QLambdaThreadWorker
internally queues the callback executions in the Qt event loop, therefore they are all serialized by Qt.
Finally, we can create as many loops as we want, just bear in mind we might need to keep track of the handles if we want to stop the cycles sometime in the future.
QLambdaThreadWorker
was made to be reusable, this means you can still move an object to the thread handled by it using, using the moveQObjectToThread
method, or just get a reference to the thread itself using the getThread
method.
Consider the case where we are tasked to design an async API for a network client, using QDeferred
the API would look as follows:
#include <QObject>
#include <QDeferred>
#include <QLambdaThreadWorker>
class MyClient : public QObject
{
Q_OBJECT
public:
explicit MyClient(QObject *parent = 0);
~MyClient();
QDeferred<QString> connect(const QString &strAddress);
QDeferred<QString> request(const QString &strRequest);
signals:
void disconnected();
private:
QLambdaThreadWorker m_worker;
};
Note that our API also uses Qt's signals, they are not incompatible and they actually serve a different purpose than QDeferred
.
Qt's signals and slots are very good for handling unexpected events, for example that our client unexpectedly disconnects. While QDeferred
is better suited for expected events, for example, after our client makes a request to the server, we expect a response soon after.
Using a well designed QDeferred
based API, we could use our client as follows:
MyClient client;
client.request("GET")
.done([](QString strGetReponse){
// this callback for the GET request
});
client.request("POST")
.done([](QString strPostReponse){
// this callback for the POST request
});
Note, the two previous calls to the request
method are async, and we don't know for sure which reponse will arrive first. But that doesn't matter because QDeferred
calls the correct callback for each request.
If we used Qt's signals and slots for this task, we would force the user of our client API to keep track of the state for each request, and figure out which reponse corresponds to which request.
Talking about state, in our client example we forgot to make sure the client is connected before we can make a request. We could fix it by nesting our async calls as follows:
MyClient client;
client.connect()
.done([&client](QString strAddress) {
// nested
client.request("GET")
.done([](QString strGetReponse) {
// do something with 'strGetReponse'
});
});
But this inmediatly looks wrong, what if the server we are connecting to, has a state and it requires for example, a series of login requests called in order? Then we would be forced to do something like this:
client.request("POST:login/user=bill,pass=123")
.done([&client](QString strResponse) {
client.request("POST:select/account_1")
.done([&client](QString strResponse) {
client.request("POST:select/january")
.done([&client](QString strResponse) {
client.request("GET:balance")
.done([&client](QString strResponse) {
// and so on ..............
});
});
});
});
This is what is called in other programming languages as the pyramid of doom, which makes the code unreadible and difficult to debug.
To solve this, QDeferred
has a very handy method called then
, which allows us to chain the async operations of our client:
MyClient client;
client.connect()
.then<QString>([&client](QString strAddress) {
// do something reponse
return client.request("POST:login/user=bill,pass=123"); // NOTE : callback returns a QDeferred<QString>
})
.then<QString>([&client](QString strResponse) {
// do something reponse
return client.request("POST:select/account_1");
})
.then<QString>([&client](QString strResponse) {
// do something reponse
return client.request("POST:select/january");
})
.then<QString>([&client](QString strResponse) {
// do something reponse
return client.request("GET:balance");
})
.done([](QString strResponse) {
// finish
});
The callback passed to the then
method will only be executed if the deferred object is resolved (success), same as in the done
method. The main difference is that the then
method accepts a template argument, which is the template argument of the expected QDeferred
object to be returned by the then
callback.
The code becomes more readible, because now the order of execution of the async operations is much more clear. It reads as:
connect, then login, then select account1, then select january, then get balance
Remember that all those callbacks are actually executed in order, and in the thread in which they are defined. If any of those steps fails, then the rest of the callbacks are not executed. We have not yet handled errors, but for each step we could define a fail
callback:
MyClient client;
client.connect()
.fail([](QString strError){
qDebug() << "Failed connecting";
})
.then<QString>([&client](QString strAddress) {
// do something reponse
return client.request("POST:login/user=bill,pass=123"); // NOTE : callback returns a QDeferred<QString>
})
.fail([](QString strError){
qDebug() << "Failed logging in";
})
.then<QString>([&client](QString strResponse) {
// do something reponse
return client.request("GET:balance");
})
.fail([](QString strError){
qDebug() << "Failed getting balance";
})
.done([](QString strResponse) {
// finish
});
This way we can know exactly in which step something went wrong.
There is another very handy feature of the QDeferred
that allows notifying partial progress of an async operation.
Say we want to count from 1 to 10 in a thread and notify every time the count changes:
QLambdaThreadWorker worker;
QDeferred<int> countToTen()
{
QDeferred<int> retDefered;
QSharedPointer<int> handle(new int);
QSharedPointer<int> counter(new int);
// init counter
*counter = 0;
*handle = worker.startLoopInThread([retDefered, handle, counter]() mutable {
// notify progress
retDefered.notify(++*counter);
// end counting
if (*counter == 10)
{
// stop thread
worker.stopLoopInThread(*handle);
retDefered.resolve(*counter);
}
}, 1000);
return retDefered;
}
We use the notify
method to notify progress of our async code execution. We pass to the method the current value of the progress.
Note we needed to create handle
and counter
in the heap, because those variables need to exist throughout the whole counting life-cycle.
To susbcribe to the progress we use the progress
method and pass a callback to it. The coutning function is called as follows:
countToTen()
.progress([](int counter) {
qDebug() << "Progress" << counter;
})
.done([](int counter) {
qDebug() << "Finished" << counter;
});
The output would be:
Progress 1
Progress 2
Progress 3
Progress 4
Progress 5
Progress 6
Progress 7
Progress 8
Progress 9
Progress 10
Finished 10
Do not let QDeferred
fool you into thinking that the progress type cannot be separated from the result type. In the previous example, the type used to report progress <int>
is also used to resolve the result. Bet let's not forget that QDeferred
accepts N template types, so we could separate progress and result types as follows:
QDeferred<QByteArray, int, QString> defer = myMethod()
.progress([](QByteArray result, int progress, QString message) {
Q_UNUSED(result);
qDebug() << "Progress" << progress << "% , details :" << message;
})
.done([](QByteArray result, int progress, QString message) {
Q_UNUSED(progress, message);
// do something with the QByteArray results
});
In the previous snippet, progress types are (arbitrarily) selected to be int, QString
, while result type is QByteArray
. The fact that we can define N template types does not mean we have to use all of them in all callbacks. We can just use some of them for progress and pass in default/empty values for the others. Then use the others for results and ignore the types used for progress. This gives QDeferred
a lot of flexibility to define whatever types the user deems necessary for each use case.
Consider a simplified version of our network client example:
struct MyClient
{
QDefer connect()
{
QDefer retDefer;
m_worker.execInThread([retDefer]() mutable {
// sleep random 0 to 5 secs before resolving
static int seed = 123;
qsrand(++seed);
QThread::sleep(qrand() % 5);
// resolve
retDefer.resolve();
});
return retDefer;
};
QLambdaThreadWorker m_worker;
};
The client has a connect
method that gets executed in a thread, and resolves after a random delay between 0 and 5 seconds.
We create three instances of the client, and would like to execute some code once all of them have been connected. This could complicate our code, but luckly QDeferred
has a handly static method called QDefer::when
that allows us to sync the states of multiple QDeferred
instances:
MyClient client1, client2, client3;
// connect client 1
auto defer1 = client1.connect()
.done([]() {
qDebug() << "Client 1 connected";
});
// connect client 2
auto defer2 = client2.connect()
.done([]() {
qDebug() << "Client 2 connected";
});
// connect client 3
auto defer3 = client3.connect()
.done([]() {
qDebug() << "Client 3 connected";
});
// execute callback when all connected
QDefer::when(defer1, defer2, defer3)
.done([]() {
qDebug() << "All clients connected";
});
One execution of the previous code could output:
Client 3 connected
Client 2 connected
Client 1 connected
All clients connected
The next execution could output:
Client 3 connected
Client 1 connected
Client 2 connected
All clients connected
It doesn't matter which QDeferred
instances are resolved first or when they are resolved, the QDefer::when
method waits (not-blocking) until all of them are resolved and then executes its own done
callback. Of course if at least one QDeferred
is rejected, then the fail
callback of the QDefer::when
method is called.
It is not necesary that all the QDeferred
instances passed to the QDefer::when
method are of the same template type. The following code is also valid:
QDeferred<int> defer1;
QDeferred<double> defer2;
QDeferred<QList<QString>> defer3;
// ...
QDefer::when(defer1, defer2, defer3)
.done([]() {
qDebug() << "All done";
})
.fail([](){
qDebug() << "At least one failed";
});
One last thing to mention is that once a QDeferred
instances has been resolved or rejected, it is not possible to resolve or reject it again.
Therefore sometimes it might be handy to test the state of a deferred instance before trying to resolve/reject it:
QDeferred<int> defer;
if (defer.state() == QDeferredState::PENDING)
{
defer.resolve(123);
}
The QDeferredState
enum has only three possible values:
-
PENDING
: not resolved nor rejected. -
RESOLVED
: already resolved. -
REJECTED
: already rejected.
Using the state
method we can now if an async operation has been already processed.
We don't need a thread to use QDeferred
. Very much as Qt's signals and slots, QDeferred
accepts a Qt::ConnectionType as an argument to each of its consumer API callbacks (done
, fail
and progress
). This gives control over which thread is used to execute the callback, by default all callbacks have a Qt::AutoConnection
connection type.
// Qt::DirectConnection explicitly forces to execute the 'progress' in the same thread in which 'notify' is called
// but this would happen by default anyway because Qt::AutoConnection figures aout both methods exist in the same thread.
defer.progress([](int val) {
qDebug() << "Counting in the same thread :" << val;
}, Qt::DirectConnection);
for (int i = 0; i < 100; i++)
{
// Will print de debug message inmediatly
defer.notify(i + 1);
}
If we would like to wait until the next execution of the Qt event loop we could force it as follows:
// The debug messages will be executed 'later' on the next Qt event loop iteration
defer.progress([](int val) {
qDebug() << "Counting in the same thread :" << val;
}, Qt::QueuedConnection);
for (int i = 0; i < 100; i++)
{
defer.notify(i + 1);
}
A QDeferred
instance is an explicitly shared object, this means it can be passed around by copy, and all these copies reference to the same internal instance. This means we can reuse a QDeferred
instance, pass it around and subscribe to done
, fail
or progress
callbacks elsewhere in the code:
QDeferred<int> defer = myMethod()
.done([](int val) {
qDebug() << val;
})
.fail([](int val) {
qDebug() << val;
});
// we can pass "defer" around since is a explicitly shared object
// ...
// subscribe elsewhere
defer
.done([](int val) {
qDebug() << val;
})
.fail([](int val) {
qDebug() << val;
});
If the QDeferred
was already resolved/rejected when a new subscription is done, then the callback is called inmediatly (depending on the connection-type).
Qt provides its own classes to facilitate the execution of threaded async code. In many ways, very similar results can be achieved using both approaches. For example, to multiply two numbers in a thread:
// Using QtConcurrent
QFuture<int> future;
QFutureWatcher<int> watcher;
QObject::connect(&watcher, &QFutureWatcher<int>::finished, [&future]() {
qDebug() << "Result QFuture" << future.result();
});
future = QtConcurrent::run([]() {
return 4 * 3;
});
watcher.setFuture(future);
// Using QDeferred
QLambdaThreadWorker worker;
QDeferred<int> def;
worker.execInThread([def]() mutable {
def.resolve(4 * 3);
});
def.done([](int result) {
qDebug() << "Result QDeferred" << result;
});
So both APIs allow similar results with similar ease. But QDeferred
provides a couple of advantages with respect to the QtConcurrent
approach:
- It allows for multiple template arguments.
QLambdaThreadWorker worker;
QDeferred<int, QString, QList<double>> def;
worker.execInThread([def]() mutable {
def.resolve(123, "hello", QList<double>() << 1.1 << 2.2 << 3.3);
});
def.done([](int result, QString string, QList<double> list) {
qDebug() << "Result QDeferred" << result;
qDebug() << "Result QDeferred" << string;
qDebug() << "Result QDeferred" << list;
});
En the example above we define 3 template arguments for the QDeferred
, which allows us to pass back 3 variables as a result.
- It allows chaining of async operations.
MyClient client;
client.connect()
.then<QString>([&client](QString strAddress) {
// do something reponse
return client.request("POST:login/user=bill,pass=123"); // chaining : returns a QDeferred
})
.then<QString>([&client](QString strResponse) {
// do something reponse
return client.request("GET:balance"); // chaining
})
.done([](QString strResponse) {
// finish
});
See the Handling State section for more info.
- Allows non-blocking sync of
QDeferred
instances.
QDeferred<double> defer1, defer2, defer3;
// ...
// non-blocking : execute callback when all resolved
QDefer::when(defer1, defer2, defer3)
.done([]() {
qDebug() << "All resolved";
});
Similar to what can be done with QFutureSynchronizer
, but non-blocking. See the Handling Multiple QDeferred section.
As we saw on the examples throughout this document, QDeferred
is just another tool that can be used alongside other Qt APIs for threaded and async code execution. In special, QDeferred
was designed for solving the issue of calling async code in another thread, and retrieving the result to the calling thread. Below is a list of recommended API for some specific use cases:
-
Unexpected Events
: For unexpected events within threads, Qt's signals and slots is still the best solution. For example and unexpected network client disconnection. -
Expected Events
: For calling code in a thread and retrieving results or progress to the calling thread, thenQDeferred
is recommended as an alternative ofQtConcurrent
andQFuture
. Of course if theQFuture
API fullfills your certain needs better, then by all means stick with it. You can still useQDeferred
elsewhere in your code wherever it fits better. Examples of this use case are a network client request/response cycle, or large file operations. -
Batch Processing
: For processing data in parallel, QThreadPool is the best solution. Examples can be a network server or image processing.
In regards to QLambdaThreadWorker
, it was designed as a companion to QDeferred
in order to easier manage a long term living thread. Mainly to hide thread management to the user of an API. It is better used as a member of a class:
class MyClient
{
QDefer connect()
{
QDefer retDefer;
m_worker.execInThread([retDefer]() mutable {
// here create a socket, subscribe to connect event, and when connected then...
// ...
// resolve
retDefer.resolve();
// update internal variables
m_strPeerName = socket->peerName();
});
return retDefer;
};
QDeferred<QString> getPeerName()
{
QDeferred<QString> retDefer;
m_worker.execInThread([retDefer]() mutable {
// serialize all access to data used in another thread
retDefer.resolve(m_strPeerName);
});
return retDefer;
};
QString m_strPeerName;
QLambdaThreadWorker m_worker;
};
It is adviced to serialize read and write access to data used in a thread to avoid the common pitfalls of threading (race-conditions, etc.). By serializing access to the m_strPeerName
variable in the example above we avoid to use mutexes and other constructs that can increase the complexity of our application. Access data directly (read/write) only within one thread, use mechanisms such as QDeferred
to access it indirectly between different threads and all should be fine.
Remember to make good use of the then
method:
MyClient client;
client.connect()
.then<QString>([&client](){
return client.getPeerName();
})
.done([](QString strPeerName){
qDebug() << "Connected to" << strPeerName;
});
And finally remember to make sure that any lamba captures made by reference live long enough for the execution of your callbacks. E.g. in the example above make sure client
lives until after the then
and done
callbacks are executed.
Copyright (c) 2017-Forever Juan Gonzalez Burgos