QtThreading and Concurrency

Remarks

A few notes that are already mentioned in the official docs here and here:

  • If an object has a parent, it has to be in the same thread as the parent, i.e. it cannot be moved to a new thread, nor can you set a parent to an object if the parent and the object live in different threads
  • When an object is moved to a new thread, all of its children are also moved to the new thread
  • You can only push objects to a new thread. You cannot pull them to a new thread, i.e. you can only call moveToThread from the thread where the object is currently living in

Basic usage of QThread

QThread is a handle to a platform thread. It lets you manage the thread by monitoring its lifetime, and requesting that it finishes its work.

In most cases inhering from the class is not recommended. The default run method starts an event loop that can dispatch events to objects living in the class. 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. The object can run its own code automatically e.g. by using QMetaObject::invokeMethod().

#include <QObject>

class MyWorker : public QObject
{
    Q_OBJECT
public:
    Q_SLOT void doWork() {
        qDebug() << "doWork()" << QThread::currentThread();
        // and do some long operation here
    }
    MyWorker(QObject * parent = nullptr) : QObject{parent} {}
};

class MyController : public QObject
{
    Q_OBJECT
    Worker worker;
    QThread workerThread;
public:
    MyController()  {
        worker.moveToThread(&workerThread);
        // provide meaningful debug output
        workerThread.setObjectName("workerThread");
        workerThread.start();
        // the thread starts the event loop and blocks waiting for events
    }
    ~MyController()  {
        workerThread.quit();
        workerThread.wait();
    }
    void operate() {
        // Qt::QueuedConnection ensures that the slot is invoked in its own thread
        QMetaObject::invokeMethod(&worker, "doWork", Qt::QueuedConnection);
    }
};

If your worker should be ephemeral and only exist while its work is being done, it's best to submit a functor or a thread-safe method for execution in the thread pool via QtConcurrent::run.

QtConcurrent Run

If you find managing QThreads and low-level primitives like mutexes or semaphores too complex, Qt Concurrent namespace is what you are looking for. It includes classes which allow more high-level thread management.

Let's look at Concurrent Run. QtConcurrent::run() allows to run function in a new thread. When would you like to use it? When you have some long operation and you don't want to create thread manually.

Now the code:

#include <qtconcurrentrun.h>

void longOperationFunction(string parameter)
{
    // we are already in another thread
    // long stuff here
} 

void mainThreadFunction()
{
    QFuture<void> f = run(longOperationFunction, "argToPass");
    f.waitForFinished();
}

So things are simple: when we need to run another function in another thread, just call QtConcurrent::run, pass function and its parameters and that's it!

QFuture presents the result of our asynchronous computation. In case of QtConcurrent::run we can't cancel the function execution.

Invoking slots from other threads

When a Qt event loop is used to perform operations and a non-Qt-saavy user needs to interact with that event loop, writing the slot to handle regular invocations from another thread can simplify things for other users.

main.cpp:

#include "OperationExecutioner.h"
#include <QCoreApplication>
#include <QThread>

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    QThread thrd;
    thrd.setObjectName("thrd");
    thrd.start();
    while(!thrd.isRunning())
        QThread::msleep(10);

    OperationExecutioner* oe = new OperationExecutioner;
    oe->moveToThread(&thrd);
    oe->doIt1(123,'A');
    oe->deleteLater();
    thrd.quit();
    while(!thrd.isFinished())
        QThread::msleep(10);

    return 0;
}

OperationExecutioner.h:

#ifndef OPERATION_EXECUTIONER_H
#define OPERATION_EXECUTIONER_H

#include <QObject>

class OperationExecutioner : public QObject
{
    Q_OBJECT
public slots:
    void doIt1(int argi, char argc);
};

#endif  // OPERATION_EXECUTIONER_H

OperationExecutioner.cpp:

#include "OperationExecutioner.h"
#include <QMetaObject>
#include <QThread>
#include <QDebug>

void OperationExecutioner::doIt1(int argi, char argc)
{
    if (QThread::currentThread() != thread()) {
        qInfo() << "Called from thread" << QThread::currentThread();
        QMetaObject::invokeMethod(this, "doIt1", Qt::QueuedConnection,
                                  Q_ARG(int,argi), Q_ARG(char,argc));
        return;
    }

    qInfo() << "Called from thread" << QThread::currentThread()
            << "with args" << argi << argc;
}

OperationExecutioner.pro:

HEADERS += OperationExecutioner.h
SOURCES += main.cpp OperationExecutioner.cpp
QT -= gui