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 ¶meter) method is:
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork(const QString ¶meter) {
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.
I'll try to answer all the issues you're addressing in your question:
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()
.
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.
::doWork()
function.resultReady
signal.QTimer
, you can use a cancel()
slot because that will be invoked in the thread's event loop before the next timeout.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!)
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.