1

I created a minimal QT GUI example to update widgets from a worker thread based on the recommended approach in the The QThread 5.12 documentation.

As described in the QThread 5.12 documentation, the Worker class (with a potentially long void doWork(const QString &parameter) method is:

class Worker : public QObject
{
    Q_OBJECT

public slots:
    void doWork(const QString &parameter) {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        emit resultReady(result);
    }

signals:
    void resultReady(const QString &result);
};

and the corresponding Controller class is:

class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;
public:
    Controller() {
        Worker *worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString &);
signals:
    void operate(const QString &);
};

Unlike sub-classing from a QThread, the approach shown in the documentation shows the recommended way that uses a controller and a worker that extends QObject rather than extending QThread and overriding the QThread::run method, however it does not show how these should be used in the context of a real example.

I need to use an QT Worker thread that updates widgets on a GUI using a timer.

I also need to be able to halt and restart/relaunch this thread with different parameters and I am having some trouble with how to do this correctly. indicates the preferred way to do this via a Controller and a Worker but the connect logic is a bit confusing.

The place where I need help is how to properly integrate the timer in my worker thread and also how to stop and restart a replacement worker when the current one has either finished or been interrupted and restarted.

My working code is made up of the following files.

Controller.h

#pragma once

// SYSTEM INCLUDES
#include <QObject>
#include <QThread>

// APPLICATION INCLUDES
#include "Worker.h"

// DEFINES
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STRUCTS
// TYPEDEFS
// FORWARD DECLARATIONS

class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;
public:
    Controller(/*MainWindow* mainWindow*/) {
        auto worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString &) {
        // how do I update the mainWindow from here
    }
signals:
    void operate(int);
};

Worker.h

#pragma once

// SYSTEM INCLUDES
#include <QTimer>
#include <QObject>
#include <QEventLoop>

// APPLICATION INCLUDES
#include "Worker.h"

// DEFINES
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STRUCTS
// TYPEDEFS
// FORWARD DECLARATIONS

class Worker : public QObject
{
    Q_OBJECT

public slots:
    void doWork(int count)  {
        QString result = "finished";
        // Event loop allocated in workerThread
        // (non-main) thread affinity (as moveToThread)
        // this is important as otherwise it would occur
        // on the main thread.
        QEventLoop loop;
        for (auto i=0; i< count; i++) {
            // wait 1000 ms doing nothing...
            QTimer::singleShot(1000, &loop, SLOT(quit()));
            // process any signals emitted above
            loop.exec();

            emit progressUpdate(i);
        }
        emit resultReady(result);
    }
signals:
    void progressUpdate(int secondsLeft);
    void resultReady(const QString &result);
};

MainWindow.h - I needed to add a Controller member here. I also added an updateValue slot here where I wish to update the GUI. Unfortunately I don't know how to get the controller or the worker to connect a signal from the thread to update this slot.

#pragma once

// SYSTEM INCLUDES
#include <memory>
#include <QMainWindow>

// APPLICATION INCLUDES
// DEFINES
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STRUCTS
// TYPEDEFS
// FORWARD DECLARATIONS
namespace Ui {
class MainWindow;
}

class Controller;

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();
    void updateValue(int secsLeft);

private:
    Ui::MainWindow *ui;
    std::unique_ptr<Controller> mpController;
};

MainWindow.cpp -

#include <QThread>

#include "MainWindow.h"
#include "ui_MainWindow.h"
#include "Controller.h"

MainWindow::MainWindow(QWidget *parent) 
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , mpController(std::make_unique<Controller>())
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    emit mpController->operate(100);
}

void MainWindow::updateValue(int secsLeft)
{
    ui->secondsLeft->setText(QString::number(secsLeft));
}

and finally main.cpp

#include "MainWindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

I basically need help and an explanation on how I should use the QT Thread's controller/worker integrated in my GUI.

johnco3
  • 2,401
  • 4
  • 35
  • 67

1 Answers1

3

I'll try to answer all the issues you're addressing in your question:

  1. I don't know how to get the controller or the worker to connect a signal from the thread to update this slot.

You got that almost right yourself.

Your Worker lives within the event loop of your Controller:

+--GUI-thread--+ (main event loop)
| MainWindow,  |
| Controller --o-----> +--QThread--+ (own event loop in ::exec())
+--------------+       | Worker    |
                       +-----------+

Communication between Controller and Worker must happen through signal-slot-connections. In between MainWindow and Controller signals help keep dependencies to a minimum.

You can imagine Controller as a kind of relay: Commands from MainWindow get forwarded through Controller to the Worker. Results from Worker get forwarded through the Controller to anyone who is interested.

For this, you can simply define signals in Controller:

class Controller : public QObject
{
    //...
signals:
    void SignalForwardResult(int result);
};

and then instead of

    connect(worker, &Worker::resultReady, this, &Controller::handleResults);

use the new signal:

    connect(worker, &Worker::resultReady, this, &Controller::SignalForwardResult);
    // Yes, you can connect a signal to another signal the same way you would connect to a slot.

and in your MainWindow constructor:

//...
ui->setupUi(this);
connect(mpController, &Controller::SignalForwardResult, this, &MainWindow::displayResult);

Likewise for Worker::progressUpdate() -> Controller::SignalForwardProgress() -> MainWindow::updateValue().


  1. how to stop and restart a replacement worker when the current one has either finished or been interrupted and restarted.

Either create a new worker for each task or use a persistent worker that can react on new task requests.

  • You start a task by sending it to the worker ::doWork() function.
  • A task ends by itself when the long work is finished. You get a notification via the worker's resultReady signal.
  • Cancelling a task is only possible by intervention
    • If you indeed have a QTimer, you can use a cancel() slot because that will be invoked in the thread's event loop before the next timeout.
    • If you have a long-running calculation, you need to share some token that you read from inside your calculation method and set from your GUI thread. I usually use a shared QAtomicInt pointer for that, but a shared bool usually suffices too.

Note that while a method is running on a thread, that thread's event loop is blocked and won't receive any signals until the method is finished.

DON'T use QCoreApplication::processEvents() except if you really know, what you're doing. (And expect that you don't!)


  1. how to properly integrate the timer in my worker thread

You shouldn't.

  • I guess you use a background thread because there is so much work to do or you need to blocking wait for so long that it would block the GUI, right? (If not, consider not using threads, saves you a lot of headaches.)

  • If you need a timer, make it a member of Worker and set its parentObject to the Worker instance. This way, both will always have the same thread affinity. Then, connect it to a slot, like Worker::timeoutSlot(). There you can emit your finish signal.

Martin Hennings
  • 16,418
  • 9
  • 48
  • 68
  • thanks for your detailed answer. BTW I was able to make some progress before I saw your answer - I created a GitHub repo `https://github.com/johnco3/QtWorkerGUI` with a very small working GUI example. The thing I need (with this trivial example) is to terminate the current countdown and start again. It seems to do that by simply starting the controller again - but I don't see where the prior thread is terminated and restarted (as the resetting in GUI timer would seem to suggest), it counts down from 10 and when I click reset it rests back to 10 and counts down again. – johnco3 Jan 14 '19 at 17:51
  • Where the thread is terminated: `connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);`. The invocation of `deleteLater()` is what causes the `QThread` to be disposed of. In your OP, that is triggered through the destruction of `Controller`. – user268396 Jan 14 '19 at 19:12
  • @johnco3 Your background thread isn't terminated until your Controller goes out of scope (when MainWindow is closed). Instead, `Worker::doWork()` is executed in `Controller::workerThread`'s event loop. After finishing, the background thread's event loop is idle again until you invoke `doWork()` the next time. – Martin Hennings Jan 15 '19 at 14:41
  • @MartinHennings - sorry I didn't get back to you earlier (I was away), your answer is excellent, BTW if you have time - I never understood how the restart timer button works under the covers in my GUI (but it clearly does so - https://github.com/johnco3/QtWorkerGUI). I can see the timer being restarted while counting down, but when I click reset to make the main thread emit mpController->requestWork(10) - I immediately enter the doWork again using the same thread ID with the previous counter, I never see the previous doWork exit, it just re-enters its doWork loop, very confusing, thanks. – johnco3 Feb 01 '19 at 17:35
  • @MartinHennings - I found the reason the reset immediately looked like it was working, when I click reset, this initiates a recursive call to schedule a new doWork on top of the existing one, I see multiple calls to doWork back in the call stack. That was not what I intended, it looks like when my doWork method finishes (and emits finished) it never terminates the worker. I intended to use the persistent worker that reacts on new task requests - but I am curious as to how an existing task/thread would terminate cleanly and not cause stack buildup. – johnco3 Feb 01 '19 at 17:44
  • That's the mess that you get when (implicitly) calling `processEvents()`, which is being called by `QEventLoop::exec()`. The `doWork()` invocations are not recursive, just simultaneously. If you replace the timer and the exec() by a simple `msleep(1000)`, you will get all your progress signals in the correct order, and each click on `restart` will put another `doWork()` in the pipeline (the event loop of your workerThread), waiting for the previous invocation to finish. – Martin Hennings Feb 04 '19 at 10:46