QThread::wait() does not return without using direct connection

2.3k views Asked by At

I got some trouble with Qt Threads and Connections. I found several tutorials and discussions on this topic, I followed this tutorial to create the thread. But I still got the problem, that calling wait() on the thread never returns and the UI freezes.

A similar question was asked here before (the second example): Qt connection type between threads: why does this work?

In the last edit of the question, the author mentions that he had created a deadlock. I assume, I do the same in my application. But I still do not understand, why this happens. Reading the suggested article did not help me understanding. I just got the point, that deadlocks can happen, but I don't know, what's causing it there or in my case.

I have also created an example that's reduced to the core problem. Find the code at the bottom of this question.

So my questions are: What exactly is the cause for the deadlock in my example? Is there a solution without making the connection a direct connection?

I'd really appreciate any hints.

Thanks!

EDIT:

Because of the comments I tried it to send the stop request via a signal and I added a QCoreApplication::processEvents() call in the thread loop. But the main problem is still the same.

EDIT2:

I found an acceptable solution, after thinking a bit more about event loops:

thread.requestStop();

// now instead of using wait(), we poll and keep the event loop alive
// polling is not nice, but if it does not take a very long time
// for the thread to finish, it is acceptable for me.
while (thread.isRunning())
{
    // This ensures that the finished() signal
    // will be processed by the thread object
    QCoreApplication::processEvents();        
}

This actually works and the worker itself controls how to stop working.

After coming up with this, I also have an explanation for the freezing issue: Calling wait seems to keep the main thread either busy or suspended so it does not process any events. Since the thread object lives in the main thread, the thread's finished() signal is enqued but never processed.

My implicit assumption, that thread.wait() would still keep the event loop working, was obviously wrong. But then, what's the QThread::wait() function good for?!?

This is just a theory, but maybe someone here can verify or falsify it...

EDIT 3 (final solution):

After reading this small article and implmenting a subclassing solution, I think that this is preferable for this particular proble. There is no need for an event loop and I'm fine with direct calls on a different thread and using mutex protection. It's less code, easier to understand and easier to debug.

I think I would use the non-subclassing strategy only, if there were more interaction with the thread than just start and pause.


My reduced Example

Maybe I should point out, that I do not delete the thread, because in my original application, I want to resume later, so stopping it actually means pausing it.

worker.h:

#ifndef WORKER_H
#define WORKER_H

#include <QObject>
#include <QMutex>

class Worker : public QObject
{
    Q_OBJECT

public:
    explicit Worker(QObject* parent = NULL);

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

signals:
    void finished();

private:

    bool stopRequested;
    QMutex mutex;
};

#endif // WORKER_H

worker.cpp:

#include "worker.h"

#include <QThread>
#include <iostream>

using namespace std;

Worker::Worker(QObject *parent)
    : stopRequested(false)
{
}

void Worker::doWork()
{
    static int cnt = 0;

    // local loop control variable
    // to make the usage of the mutex easier.
    bool stopRequesteLocal = false;

    while (!stopRequesteLocal)
    {
        cout << ++cnt << endl;
        QThread::msleep(100);

        mutex.lock();
        stopRequesteLocal = stopRequested;
        mutex.unlock();
    }

    cout << "Finishing soon..." << endl;

    QThread::sleep(2);
    emit finished();
}

void Worker::requestStop()
{
    mutex.lock();
    stopRequested = true;
    mutex.unlock();
}

main program:

#include <QCoreApplication>
#include <QThread>
#include <QtCore>
#include <iostream>

#include "worker.h"

using namespace std;

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QThread thread;
    Worker worker;


    QObject::connect(&thread, SIGNAL(started()), &worker, SLOT(doWork()));

    // this does not work:
    QObject::connect(&worker, SIGNAL(finished()), &thread, SLOT(quit()));

    // this would work:
    //QObject::connect(&worker, SIGNAL(finished()), &thread, SLOT(quit()), Qt::DirectConnection);

    // relocating the moveToThread call does not change anything.
    worker.moveToThread(&thread);

    thread.start();

    QThread::sleep(2);

    worker.requestStop();
    cout << "Stop requested, wait for thread." << endl;
    thread.wait();
    cout << "Thread finished" << endl;

    // I do not know if this is correct, but it does not really matter, because
    // the program never gets here.
    QCoreApplication::exit(0);
}
3

There are 3 answers

0
Kanalpiroge On BEST ANSWER

I added my own answere to the question text as EDIT 3.

3
Cool_Coder On

It doesn't look like you have read that article completely.

QThread* thread = new QThread;
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString)));
connect(thread, SIGNAL(started()), worker, SLOT(process()));
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();

You have only implemented partially of what is suggested on the article.

QThread::wait() will wait until QThread::finished() is emitted from doWork(). Please emit this SIGNAL if you need to quit from this thread and return to the main thread. For this you need to keep a reference of the thread on which this object is moved to.

6
TheDarkKnight On

The first issue I see is that you're not using signals and slots to communicate between the objects running on different threads; main and the new thread that hosts the worker object.

You move the worker object to the second thread, but call a function on the worker object from the main thread: -

thread.start();
QThread::sleep(2);
worker.requestStop(); // Aaahh, this is running on the new thread!!!

Considering that a thread has its own stack and registers I really don't see how this is safe.

If you use signals and slots, Qt handles a lot of threading issues. While you should be able to use a variable to control the 2nd thread, it would also be cleaner to use signals and slots.

Note that when a signal is emitted from one thread, a message is posted to the thread of the receiving object, if the sender and receiver are on separate threads.

Convert your code to use signals and slots for communicating between objects on different threads and your deadlock should disappear.