In this quick post we’ll take a look at Qt’s elegant and simple QFuture, QtConcurrentRun, and QFutureWatcher interfaces. Using these classes allows us to consume a QThreadPool thread for a specific function.
This is handy for many operations, and while simplistic in that we’re not synchronizing data, still has many uses, from processing large blocks of images to streaming chunks of data.
In this post we’ll check out some sample code for implementing QFuture.
Let’s start with the following code which implements QFuture in the most basic manner possible:
-
#include
-
#include
-
#include
-
#include
-
-
int computation(int x){
-
long a,b,c;
-
a = 1; b = 1;
-
for(int i = 0; i < x; i++){
-
c = b + a;
-
a = b;
-
b = c;
-
}
-
return c;
-
}
-
-
int main(int argc, char *argv[])
-
{
-
QCoreApplication a(argc, argv);
-
-
int x = 5;
-
QFuture future = QtConcurrent::run(computation, x);
-
// blocking…
-
future.waitForFinished();
-
-
int result = future.result();
-
qDebug(QString::number(result).toStdString().c_str());
-
-
return a.exec();
-
}
This code will consume a thread for the computation method call, returning the result via future.result(). When compared to our raw POSIX thread example it becomes clear just how much more simple the Qt version is.
Simple enough, but this code has one potential problem: as the comment shows, waitForFinished() blocks, which means our GUI stalls.
To fix this we’ll implement a signal/slot solution to let the long-running process chug away in the background, leaving the GUI thread free for updates. Of course the only trick to this is we need signals and slots, which means we must break our application apart into traditional header and implementation files.
main.cpp
-
#include "mainwindow.h"
-
-
int main(int argc, char *argv[])
-
{
-
QApplication a(argc, argv);
-
MainWindow w;
-
w.show();
-
return a.exec();
-
}
mainwindow.cpp
-
#include "thread.h"
-
-
MainWindow::MainWindow(QWidget *parent)
-
: QMainWindow(parent)
-
{
-
-
setWindowTitle("Threads Demo");
-
-
startButton = new QPushButton("Start Thread…", this);
-
connect(startButton, SIGNAL(clicked()), this, SLOT(startButtonPushed()));
-
-
QGridLayout *mainLayout = new QGridLayout;
-
mainLayout->addWidget(startButton);
-
-
thread = new Thread(this);
-
connect(thread, SIGNAL(threadFinished(int)), this, SLOT(threadDone(int)));
-
-
}
-
-
MainWindow::~MainWindow()
-
{
-
-
}
-
-
void MainWindow::startButtonPushed()
-
{
-
// we pass a pointer to the object, the address of the method, and the arg
-
future = QtConcurrent::run(thread, &Thread::compute, 10);
-
}
-
-
void MainWindow::threadDone(int x){
-
qDebug("threadDone");
-
qDebug(QString::number(x).toStdString().c_str());
-
}
mainwindow.h
-
#define MAINWINDOW_H
-
-
#include
-
#include
-
#include
-
-
#include
-
-
#include
-
#include
-
#include
-
-
#include "thread.h"
-
-
class MainWindow : public QMainWindow
-
{
-
Q_OBJECT
-
-
public:
-
MainWindow(QWidget *parent = 0);
-
~MainWindow();
-
-
Thread *thread;
-
QFuture future;
-
-
public slots :
-
void startButtonPushed();
-
void threadDone(int);
-
-
signals :
-
-
private :
-
QPushButton *startButton;
-
};
-
-
#endif // MAINWINDOW_H
thread.cpp
-
-
Thread::Thread(QObject *parent) : QObject(parent) { }
-
-
Thread::~Thread() { }
-
-
void Thread::compute(int x){
-
long a,b,c;
-
a = 1; b = 1;
-
for(int i = 0; i < x; i++){
-
c = b + a;
-
a = b;
-
b = c;
-
}
-
-
qlonglong h,j,u;
-
j = 1; u = 1;
-
for(int j = 0; j < 100000000; j++){
-
h = (u * j);
-
u = (h + 1) + j;
-
-
for(int w = 0; w < 40000000; w++){
-
u = (j + 2) * 3;
-
j = (u + 2);
-
h = (u * 2);
-
}
-
}
-
-
emit threadFinished(a);
-
}
thread.h
-
#define THREAD_H
-
-
#include
-
#include
-
-
class Thread : public QObject
-
{
-
Q_OBJECT
-
-
public :
-
Thread(QObject *parent = 0);
-
~Thread();
-
void compute(int);
-
-
public slots :
-
-
signals :
-
void threadFinished(int);
-
-
};
-
-
#endif // THREAD_H
Between this and the official documentation it should be pretty clear what’s going on.
However. the one thing to point out is that time and again we’ll be confronted with the question of how do we ‘get’ values back from a signal/slot architecture. Of course in this example we’re using threads, but this question applies equally well to non-threaded scenarios.
In many instances the answer to this question is we needn’t do anything. We’re processing a batch of images and only need to know when the batch operation is complete. This can be in the form of a dialog box, and is where QFutureWatcher comes in–we only care about being notified as to when we’re done processing.
But that’s not always good enough, and is for me at least, where much of the learning pains of signal/slots has come from.
In this example I wanted to return the value of our threaded computation to the main thread via qDebug(). The problem was that while QFuture returns a value via future.result(), we cannot get this value asynchronously without blocking the GUI, as was the case in the first example. Why? Because when we implement QFutureWatcher to notify us the result of a QFuture has finished, we now only have signals, and these signals do not contain the results of our method call.
So how do we get our results?
In the end my solution was simple: bypass QFutureWatcher and emit a signal directly from the threaded computation method. This has the exact same effect as emitting a signal when the thread is done, only now we get a value back as well.
In the end my original failure was because I was trying to force functionality where it didn’t belong. QFuture is a very simple wrapper (though powerful), which makes spawning threads a snap. But it is in the end just that, a simplified wrapper for a more complex process.
Of course the downside to this is I had to implement my own signal/slot connections to retrieve the value, and in doing so, made my function a touch less portable. No matter, it’s a small price to pay as I now get my values back to the main thread as desired.
The last thing I would like to point out is the syntax of the call:
future = QtConcurrent::run(thread, &Thread::compute, 10);
Although this is covered in the official documentation I ended up stuck for a bit because I didn’t realize one very important subtlety:
When passing members functions the first parameter must be a pointer to an instance of the class. It says as much in the docs–the trick is that the second parameter must then be the address of the member function being called.
If you leave this part out you’ll get compiler errors, so make sure to double check what you’re passing.
Of course this is the most simplistic thread concept in Qt, but it’s a a darn powerful one. Study the code above, try it yourself, and get multi-threading!