Effective Threading Using Qt

Effective Threading Using Qt

Introduction

Over the years using Qt I’ve seen a lot of difficulty using threads with Qt. Threads can be difficult and Qt provides a lot of ways to make threads easy to work with. Still basic / direct / low level threading (I’ll just call this basic) is often seen as difficult with Qt. It really isn’t though.

There are three main ways I’ve seen people handle basic threading in their Qt applications. The first is using system threads, either pthread or Windows threads. I don’t like this approach because you’re basically writing a portable thread library. You can use an already built portable thread library but why use a non-Qt solution when Qt already provides portable threading.

The two other approaches are defined by Qt’s QThread documentation. 1) use a QObject worker. 2) subclass QThread and reimplement the run function.

I like using a QObject worker and I think this is the best (and easiest) approach. That said, I don’t like how Qt’s documentation explains this. It uses two QObject classes to handle this. A variation of this approach is what I’m going to demonstrate.

The other documented approach is subclassing QThread. This isn’t a horrible idea but I don’t think it’s the cleanest approach. It also ties the functionality to a thread making code reuse low in this case. You could get around this limitation by putting the functionality in a separate class but now you have two classes and the thread subclass really isn’t necessary when using the QObject worker approach.

The Example Code

This is a very simple example that demonstrates two types of workers. One takes arguments and runs until it’s task is finished. The second on runs until it’s told to stop. The second worker could take arguments if you need it to. For this example the workers both just increment a count every second and send the result to a QMainWindow for it to be displayed.

Here is all the code that goes into the example which will have the main parts explained after:

main.cpp

#include <QApplication>

#include <mainwindow.h>

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

    MainWindow w;
    w.show();

    return app.exec();
}

mainwindow.h

#ifndef __MAIN_WINDOW_H__
#define __MAIN_WINDOW_H__

#include <QMainWindow>

#include "ui_mainwindow.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT

    public:
        MainWindow(QWidget *parent=0);

    private slots:
        void updateCount(int cnt);
        void updateInfiniteCount(int cnt);

        void startCount();
        void startInfiniteCount();

        void countFinished();
        void infiniteCountFinished();

    private:
        void connectSignalsSlots();

        bool countRunning;
        bool infiniteCountRunning;

        Ui_MainWindow ui;
};

#endif // __MAIN_WINDOW_H__

mainwindow.cpp

#include <QMessageBox>
#include <QThread>

#include "mainwindow.h"
#include "countworker.h"
#include "infinitecountworker.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      countRunning(false),
      infiniteCountRunning(false)
{
    ui.setupUi(this);
    connectSignalsSlots();
}

void MainWindow::updateCount(int cnt)
{
    ui.countOut->setText(QString::number(cnt));
}

void MainWindow::updateInfiniteCount(int cnt)
{
    ui.infiniteCountOut->setText(QString::number(cnt));
}

void MainWindow::startCount()
{
    QThread     *workerThread;
    CountWorker *worker;

    if (countRunning) {
        QMessageBox::critical(this, "Error", "Count is already running!");
        return;
    }

    workerThread = new QThread;
    worker       = new CountWorker(ui.countStart->value(), ui.countEnd->value());
    worker->moveToThread(workerThread);
    connect(workerThread, SIGNAL(started()), worker, SLOT(doWork()));
    connect(worker, SIGNAL(finished()), workerThread, SLOT(quit()));
    connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
    connect(worker, SIGNAL(finished()), this, SLOT(countFinished()));
    connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
    connect(worker, SIGNAL(updateCount(int)), this, SLOT(updateCount(int)));
    workerThread->start();

    countRunning = true;
}

void MainWindow::startInfiniteCount()
{
    QThread             *workerThread;
    InfiniteCountWorker *worker;

    if (infiniteCountRunning) {
        QMessageBox::critical(this, "Error", "Infinite count is already running!");
        return;
    }

    workerThread = new QThread;
    worker       = new InfiniteCountWorker;
    worker->moveToThread(workerThread);
    connect(workerThread, SIGNAL(started()), worker, SLOT(doWork()));
    connect(worker, SIGNAL(finished()), workerThread, SLOT(quit()));
    connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
    connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
    connect(worker, SIGNAL(finished()), this, SLOT(infiniteCountFinished()));
    connect(worker, SIGNAL(updateCount(int)), this, SLOT(updateInfiniteCount(int)));
    connect(ui.infiniteCountNoGo, SIGNAL(clicked()), worker, SLOT(stopWork()));
    workerThread->start();

    infiniteCountRunning = true;
}

void MainWindow::countFinished()
{
    countRunning = false;
}

void MainWindow::infiniteCountFinished()
{
    infiniteCountRunning = false;
}

void MainWindow::connectSignalsSlots()
{
    connect(ui.countGo, SIGNAL(clicked()), this, SLOT(startCount()));
    connect(ui.infiniteCountGo, SIGNAL(clicked()), this, SLOT(startInfiniteCount()));
}

mainwindow.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>481</width>
    <height>348</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Threader</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QGroupBox" name="groupBox">
      <property name="title">
       <string>Count</string>
      </property>
      <layout class="QGridLayout" name="gridLayout">
       <item row="0" column="0">
        <widget class="QLabel" name="label">
         <property name="text">
          <string>Start:</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
         </property>
        </widget>
       </item>
       <item row="0" column="2">
        <widget class="QLabel" name="label_3">
         <property name="text">
          <string>Count:</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
         </property>
        </widget>
       </item>
       <item row="1" column="0">
        <widget class="QLabel" name="label_2">
         <property name="text">
          <string>End:</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
         </property>
        </widget>
       </item>
       <item row="1" column="2" colspan="2">
        <widget class="QPushButton" name="countGo">
         <property name="text">
          <string>Count!</string>
         </property>
        </widget>
       </item>
       <item row="0" column="3">
        <widget class="QLabel" name="countOut">
         <property name="sizePolicy">
          <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="text">
          <string>0</string>
         </property>
        </widget>
       </item>
       <item row="0" column="1">
        <widget class="QSpinBox" name="countStart"/>
       </item>
       <item row="1" column="1">
        <widget class="QSpinBox" name="countEnd">
         <property name="value">
          <number>10</number>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
    <item>
     <widget class="QGroupBox" name="groupBox_2">
      <property name="title">
       <string>Infinite Count</string>
      </property>
      <layout class="QGridLayout" name="gridLayout_2">
       <item row="0" column="0">
        <widget class="QLabel" name="label_4">
         <property name="text">
          <string>Count:</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
         </property>
        </widget>
       </item>
       <item row="0" column="1">
        <widget class="QLabel" name="infiniteCountOut">
         <property name="text">
          <string>0</string>
         </property>
        </widget>
       </item>
       <item row="1" column="0">
        <widget class="QPushButton" name="infiniteCountGo">
         <property name="text">
          <string>Start</string>
         </property>
        </widget>
       </item>
       <item row="1" column="1">
        <widget class="QPushButton" name="infiniteCountNoGo">
         <property name="text">
          <string>Stop</string>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

countworker.h

#ifndef __COUNT_WORKER_H__
#define __COUNT_WORKER_H__

#include <QObject>

class CountWorker : public QObject
{
    Q_OBJECT

    public:
        CountWorker(int start, int end);

    public slots:
        void doWork();

    signals:
        void updateCount(int);
        void finished();

    private:
        int m_countStart;
        int m_countEnd;
};

#endif // __COUNT_WORKER_H__

countworker.cpp

#include <QApplication>

#include "countworker.h"
#include "portablesleep.h"

CountWorker::CountWorker(int start, int end)
{
    m_countStart = start;
    m_countEnd   = end;
}

void CountWorker::doWork()
{
    for (int i = m_countStart; i <= m_countEnd; i++) {
        emit updateCount(i);
        qApp->processEvents();
        PortableSleep::msleep(1000);
    }
    emit finished();
}

infinitecountworker.h

#ifndef __INFINITE_COUNT_WORKER_H__
#define __INFINITE_COUNT_WORKER_H__

#include <QObject>

class InfiniteCountWorker : public QObject
{
    Q_OBJECT

    public:
        InfiniteCountWorker();

    public slots:
        void doWork();
        void stopWork();

    signals:
        void updateCount(int);
        void finished();

    private:
        bool m_running;
};

#endif // __INFINITE_COUNT_WORKER_H__

infinitecountworker.cpp

#include <QApplication>

#include "infinitecountworker.h"
#include "portablesleep.h"

InfiniteCountWorker::InfiniteCountWorker()
    : m_running(true)
{
}

void InfiniteCountWorker::doWork()
{
    int i = 0;
    while (m_running) {
        emit updateCount(i);
        i++;
        qApp->processEvents();
        PortableSleep::msleep(1000);
    }
    emit finished();
}

void InfiniteCountWorker::stopWork()
{
    m_running = false;
}

portablesleep.h

#ifndef __PORTABLE_SLEEP_H__
#define __PORTABLE_SLEEP_H__

#ifdef _WIN32
#  include <windows.h>
#else
#  include <unistd.h>
#endif

class PortableSleep
{
    public:
        static void msleep(unsigned int milliSec)
        {
#ifdef _WIN32
            Sleep(milliSec);
#else
            usleep(milliSec * 1000);
#endif
        };
};

#endif // __PORTABLE_SLEEP_H__

threader.pro

TEMPLATE    = app
CONFIG     += qt
QT         += widgets
TARGET      = threader

HEADERS    += mainwindow.h 
              countworker.h 
              infinitecountworker.h 
              portablesleep.h

SOURCES    += main.cpp 
              mainwindow.cpp 
              countworker.cpp 
              infinitecountworker.cpp

FORMS      += mainwindow.ui

MOC_DIR     = build
OBJECTS_DIR = build
DEST_DIR    = .

Code Explained

CountWorker

This is a very simple object that increases a count every second. The start and end are set as part of the constructor. Setting up the object using the constructor makes sense but it’s also because to start the worker we can’t pass any arguments. This is a limitation of this method that the worker in Qt’s docs does not have. That said, I think it’s fine to use the constructor for argument passing. This method doesn’t reuse workers (not a limitation) so it’s fine. Once the worker finishes it’s work is all done and it stops.

InfiniteCountWorker

This worker is very similar to the count worker. It’s to demonstrate infinite tasks unlike CountWorker which demonstrates finite tasks.

The key to his worker is the stopWork slot. Calling this sets sets a flag to let the worker’s doWork function to exit.

*Workers

Both workers have two signals in common, updateCount and finished. UpdateCount simply sends the current count off. The MainWindow connects and uses this to, well, update the count. finished signals that the worker is done. This is used to stop the thread and takes care of cleanup. The finished signal can have multiple overloads. For example to send off a result in addition to signaling that the worker is finished. You must have a no argument signal for completion with this method. You could use a different signal name instead of overloading “finished” if you want.

One thing to think about is thread synchronization. Well, with this worker method you don’t need to worry about it. Initialization of parameters happens before the worker is moved to the thread and before the thread is even started. All passing (such as updateCount) happens using signals and slots. When passing data between threads using signals and slots Qt handles thread synchronization for you. The stopWork function is called via a signal so the function runs on the thread the work is running on between iterations of the while loop. So there is no need to wrap m_running in a mutex or other synchronization techniques.

It might be confusing that I said the stopWork function happens between iterations of the while loop in InfiniteCountWorker. This is because of the qApp->processEvents call. Without this, the signal to initiate the stopWork slot won’t be delivered until after the while loop finishes. Which is impossible in infiniteCountWorker. Remember the thread is a single thread and everything running on it is single threaded. The while loop will block anything else in the object from running unless you use qApp->processEvents to allow signals and slots to process. In the case of the CountWorker qApp->processEvents isn’t really necessary since there aren’t any signals that are delivered to it. processEvents is only necessary for signals (from outside of or within the thread the worker is running) to initiate slots in the worker. It is not necessary for the worker to send off a signal (like updateCount) to another thread that it’s connected to.

If you haven’t realized by now that with the worker method (my variation or Qt’s) the worker has an event loop. Subclassing QThread only does in some cases (see the documentation for when). You get signals into and out of worker without any additional code (except for a single processEvents call). You get this essentially for free by using a worker!

MainWindow

The workers aren’t really useful unless they can be started on a thread. That’s what the MainWindow in this example is for. The startCount and startInfiniteCount functions are the main thing to look at.

startCount Breakdown

workerThread = new QThread;
worker       = new CountWorker(ui.countStart->value(), ui.countEnd->value());

The thread and worker are created on the heap. The worker is initialized here too.

worker->moveToThread(workerThread);

Put the worker on the thread so the worker will be run on the thread we created instead of the UI thread.

connect(workerThread, SIGNAL(started()), worker, SLOT(doWork()));

This is how the worker is started when the thread is started.

connect(worker, SIGNAL(finished()), workerThread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(worker, SIGNAL(finished()), this, SLOT(countFinished()))
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));

Connect all the finished signals. When the worker finishes the thread will be stopped (quit()), the worker will be deleted via deleteLater. Qt handles deletion and takes care of it when safe. This is another advantage of using QObject workers. Also, when the worker finishes the countFinished function is called so anything in the MainWindow that needs to happen when the worker is finished is run. Again, you could have a second finished signal (overload) that passes result data back to the MainWindow. Finally, when the thread finishes (because of the worker’s finished signal calling the threads quit slot) it will be deleted by deleteLater. The deleteLater slots mean we don’t need to track the the worker or thread pointers and worry about manually deleting them.

deleteLater is very useful. We have finished connected to multiple slots. Deleting a QObject when there are events pending or within a signal handler (slot) can lead to a crash. deleteLater ensures that this won’t happen by waiting until all events are delivered. Also, you can’t delete a QObject from a thread so this also ensures the UI thread that created the worker and thread is where they are deleted. In this example this isn’t a concern but it’s nice to know that it won’t become one.

We don’t need to worry about something like dangling connections because when a QObject is deleted it automatically disconnects all signals and slots (not in all cases but that will be covered later).

connect(worker, SIGNAL(updateCount(int)), this, SLOT(updateCount(int)))

Here the count will be updated on the MainWindow as it’s incremented in the worker. Again, there is no manual thread synchronization necessary because Qt handles this as part of it’s signal and slot system.

workerThread->start();

The last line to worry about actually starts the thread which in turns starts the worker.

startInfiniteCount Breakdown

This function is very similar to startCount. The only difference is one line.

connect(ui.infiniteCountNoGo, SIGNAL(clicked()), worker, SLOT(stopWork()));

The MainWindows button that will stop the InfiniteCountWorker is connected to the InfiniteCountWorker’s stop function.

PortableSleep

portablesleep.h is a header only cross platform sleep class. This is how we create the one second delay when counting. With Qt5 you can (should) use QThreads msleep or sleep public static functions instead. Qt4 on the other hand defines these functions as protected static functions. So there is no clean way to use them aside from creating a QThread subclass and exposing them. Note that this example is not limited to Qt4. This example will run and work with both Qt4 and Qt5. A little bit of thought is all that’s needed to achieve this.

Complex Datatypes

This information isn’t specific to threading but could be very useful to really get the most of the worker concept.

This example only uses int. This is a type that can be used with Qt’s signals and slots as well as many other types such as QString. There are many complex types like your own classes or even QMap<QString, QString>> cannot be used immediately with signals and slots.

I say immediately because any class with a public constructor, copy constructor and destructor can be registered with Qt using qRegisterMetaType. This allows the object type to be used with signals and slots. For example you can use the following in the MainWindow’s constructor to allow a complex type to be used.

qRegisterMetaType<QMap<QString, QString>>("QMap<QString, QString>");

The reason we need constructor, copy constructor and destructor is because when necessary Qt will create a copy of an object and pass the copy to a slot. In the case of passing objects between threads using signals and slots a copy will be passed to the slot. Remember primitive types like int and pointers are always copies. Complex types may or may not be copies depending on the situation. Realize that if you are passing a pointer between threads you will need manual synchronization. If you’re allowing copies to be passed then you don’t need to worry about synchronization.

Style

You’ve probably noticed that in this example the old style SIGNAL and SLOT macros were used in the connect functions. This is on purpose because it supports more compilers. Also, as mentioned, the example code works with both Qt4 (it is still used) and Qt5. I don’t really like new syntax provided by C++11 either. There are too many negatives for my liking. In this case the fact that automatic connection disconnection isn’t support is pretty big. Nor is overloading signals very clean which makes using multiple version of finish not a friendly. But this is my personal preference and not required for this threading method.

Conclusion

Contrary to popular belief Qt provides very powerful basic threading capabilities. I say basic because I didn’t even mention Qt Concurrent which provides a lot of high level threading support. Oh and QThread pool wasn’t mentioned. None of the thread synchronization objects like QMutex were described either.

Even with all of the threading objects that Qt provides it’s still really easy to use threading with QObject workers. For most people this is going to be enough for offload some functionality and keep the UI from blocking. While this is the method I use quite often there are plenty of other ways. I just happen to find this to be the cleanest and easiest.