Modify Qt Gui from Background Worker Thread

Modify Qt GUI from background worker thread

Important thing about Qt is that you must work with Qt GUI only from GUI thread, that is main thread.

That's why the proper way to do this is to notify main thread from worker, and the code in main thread will actually update text box, progress bar or something else.

The best way to do this, I think, is use QThread instead of posix thread, and use Qt signals for communicating between threads. This will be your worker, a replacer of thread_func:

class WorkerThread : public QThread {
void run() {
while(1) {
// ... hard work
// Now want to notify main thread:
emit progressChanged("Some info");
}
}
// Define signal:
signals:
void progressChanged(QString info);
};

In your widget, define a slot with same prototype as signal in .h:

class MyWidget : public QWidget {
// Your gui code

// Define slot:
public slots:
void onProgressChanged(QString info);
};

In .cpp implement this function:

void MyWidget::onProgressChanged(QString info) {
// Processing code
textBox->setText("Latest info: " + info);
}

Now in that place where you want to spawn a thread (on button click):

void MyWidget::startWorkInAThread() {
// Create an instance of your woker
WorkerThread *workerThread = new WorkerThread;
// Connect our signal and slot
connect(workerThread, SIGNAL(progressChanged(QString)),
SLOT(onProgressChanged(QString)));
// Setup callback for cleanup when it finishes
connect(workerThread, SIGNAL(finished()),
workerThread, SLOT(deleteLater()));
// Run, Forest, run!
workerThread->start(); // This invokes WorkerThread::run in a new thread
}

After you connect signal and slot, emiting slot with emit progressChanged(...) in worker thread will send message to main thread and main thread will call the slot that is connected to that signal, onProgressChanged here.

P.s. I haven't tested the code yet so feel free to suggest an edit if I'm wrong somewhere

Using Controller and QT Worker in a working GUI example

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.

Qt - updating main window with second thread

but the problem is that, i cannot reach the
ana->ui->horizontalLayout_4->addWidget(label);

Put your UI modifications in a slot in your main window, and connect a thread signal to that slot, chances are it will work. I think only the main thread has access to the UI in Qt. Thus if you want GUI functionality, it must be there, and can be only signaled from other threads.

OK, here is a simple example. BTW, your scenario doesn't really require to extend QThread - so you are better off not doing it, unless you really have to. That is why in this example I will use a normal QThread with a QObject based worker instead, but the concept is the same if you subclass QThread:

The main UI:

class MainUI : public QWidget
{
Q_OBJECT

public:
explicit MainUI(QWidget *parent = 0): QWidget(parent) {
layout = new QHBoxLayout(this);
setLayout(layout);
QThread *thread = new QThread(this);
GUIUpdater *updater = new GUIUpdater();
updater->moveToThread(thread);
connect(updater, SIGNAL(requestNewLabel(QString)), this, SLOT(createLabel(QString)));
connect(thread, SIGNAL(destroyed()), updater, SLOT(deleteLater()));

updater->newLabel("h:/test.png");
}

public slots:
void createLabel(const QString &imgSource) {
QPixmap i1(imgSource);
QLabel *label = new QLabel(this);
label->setPixmap(i1);
layout->addWidget(label);
}

private:
QHBoxLayout *layout;
};

... and the worker object:

class GUIUpdater : public QObject {
Q_OBJECT

public:
explicit GUIUpdater(QObject *parent = 0) : QObject(parent) {}
void newLabel(const QString &image) { emit requestNewLabel(image); }

signals:
void requestNewLabel(const QString &);
};

The worker object is created and moved to another thread, then connected to the slot that creates the labels, then its newLabel method is invoked, which is just a wrapper to emit the requestNewLabel signal and pass the path to the image. The signal is then passed from the worker object/thread to the main UI slot along with the image path parameter and a new label is added to the layout.

Since the worker object is created without parent in order to be able to move it to another thread, we also connect the thread destroyed signal to the worker deleteLater() slot.

Modify Widgets in non-GUI thread in Qt?

For the first example use signals and slots. QWidget::setEnabled() is a slot. create a signal modifyWidgetEnableState(bool) and connect to the slot. trigger the signal instead of using the direct method call.

the application hangs for a while due to the enormous painting activity

Are these widgets Qt build in? If yes I doubt the hang is due to the painting. Are these widgets yours? Then you may be doing too much processing in your subclassed event handlers.
The first thing to do will be to try to improve the performance of whatever you doing, the second thing will be to move the heavy processing in threads, and (again) use signals and slots to communicate with the widgets.

EDIT:
Assume o1 and o2 and are moved to different threads t1 and t2.

Whenever o2->slot() is executed as a normal function call then it is the calling thread which is executing slot(). Remember, slot() after all is a normal C++ method. Whenever a signal sig() connected to slot() is triggered, then it is the receiver thread which is executing slot(). So choose either one or the other, otherwise you will be subject to race conditions.

How to properly execute GUI operations in Qt main thread?

If you do not want to make your TCP class a QObject another option is to use the QMetaObject::invokeMethod() function.

The requirement then is that your destination class must be a QObject and you must call a slot defined on the destination.

Say your QObject is defined as follow:

class MyQObject : public QObject {
Q_OBJECT
public:
MyObject() : QObject(nullptr) {}
public slots:
void mySlotName(const QString& message) { ... }
};

Then you can call that slot from your TCP Class.

#include <QMetaObject>

void TCPClass::onSomeEvent() {
MyQObject *myQObject = m_object;
myMessage = QString("TCP event received.");
QMetaObject::invokeMethod(myQObject
, "mySlotName"
, Qt::AutoConnection // Can also use any other except DirectConnection
, Q_ARG(QString, myMessage)); // And some more args if needed
}

If you use Qt::DirectConnection for the invocation the slot will be executed in the TCP thread and it can/will crash.

Edit: Since invokeMethod function is static, you can call it from any class and that class does not need to be a QObject.

Proper way to run managable background thread with QThread

You don't need any magic and "10 signals/slots". Just create your worker:

class Worker: public QObject
{
...
public slots:
void routineTask();
}

Somewhere in your code:

QThread bckgThread;
bckgThread.start();
Worker worker;
worker.moveToThread(&bckgThread);

Connect some signal to the routineTask slot to call it or use QMetaObject::invokeMethod.
And when you are done with the thread, just call:

bckgThread.quit();
bckgThread.wait();

That's pretty simple pattern. Why go the hard way and subclass QThread?



Related Topics



Leave a reply



Submit