简体   繁体   中英

Qt thread execution does not stop after exit() quit() or terminate() was called

First off, I have read up on QThread s and using the QEventLoop but I am not entirely sure my implementation is correct.

TL;DR see Problem Details below.

Most useful sources of information is Qt Wiki , KDAB Qthread presentation (useful for w/ & w/o event loop), SO posts here and here relating to this question.

My scenario is:

I have a potentially very long running function with multiple I/O disk calls. Thus I need a thread to not block the UI. For this, I made my own implementation of a Thread.

TL;DR QThreads

My understanding is a QThread is a separate event-loop object, and requires either a custom run() implementation or have an object moved to the newly created thread object wherein the moved object(s) lives (and runs). What I described is w/ the event loop implementation.

Problem

I maybe be missing something as my implementation of this, what I described above, does not function correctly. How do I know this, well Qt Docs and SO posts above mention that QThread::quit() or QThread::exit() are dependent on the QEventLoop , and if the QThread::exec() was not run (by calling the QThread::run() via QThread::start() ), then the quit() or exit() functions will never run, which is my problem .

My implementation philisophy is somethings similar to Java's Thread & Lambda syntax eg

new Thread(() -> { // some code to run in a different thread}).start();

I used the following implementation

Thread Object container of sorts where lambdas can be used

QWorkerThread: public QObject

    // This is the thread that runs object below
----QWaitThread : public QThread

    // This is the object which lives inside the above thread
----ThreadWorker : public QObject, public QInterruptable

Simple example usage would be (thread and child object cleanup done inside QWorkerThread ):

QWorkerThread *workerThread = new QWorkerThread;
workerThread->setRunnable([](){
    // insert CPU intensive or long running task here
});
workerThread->start();

Problem Detail/Example

// somewhere in main UI thread
workerThread->stop(); // or workerThread->kill() 

which calls QThread::quit() or QThread::quit() , then QThread::terminate() followed by QThread::wait() will not terminate the thread. The long running process defined in the lambda (inside setRunnable() ) will run until it is complete.

I know this post is longer that what is conventional, but I would prefer everyone to get the full 'picture' of what I am trying to achieve, as I am unsure of where my problem actually lies.

Any help would be gratefully appreciated!


Code Implementation

I will be posting all code for a full idea of implementation, incase I miss something important.

QWaitThread.h is the implementation of a QThread

#ifndef QWAITTHREAD_H
#define QWAITTHREAD_H

#include <QObject>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>

class QWaitThread : public QThread
{
    Q_OBJECT
public:
    explicit QWaitThread(QObject *parent = nullptr);
    ~QWaitThread();
    virtual void pause();
    virtual void resume();    

signals:
    void paused();
    void resumed();

public slots:
    void pausing();
    void resuming();

private:
    QWaitCondition *waitCondition;
    QMutex mutex;
};

#endif // QWAITTHREAD_H

QWaitThread.cpp

#include "qwaitthread.h"

QWaitThread::QWaitThread(QObject *parent) : QThread(parent)
{
    waitCondition = new QWaitCondition;
}

QWaitThread::~QWaitThread()
{
    if(waitCondition != nullptr) {
        delete waitCondition;
    }
}

void QWaitThread::pause()
{
    emit paused();
    waitCondition->wait(&mutex);
}

void QWaitThread::resume()
{
    waitCondition->wakeAll();
    emit resumed();
}

void QWaitThread::pausing()
{
    pause();
}

void QWaitThread::resuming()
{
    resume();
}

QInterruptable.h interface defines some expected functionality

#ifndef QINTERRUPTABLE_H
#define QINTERRUPTABLE_H

class QInterruptable {
public:
    virtual void pause() = 0;
    virtual void resume() = 0;
    virtual void interrupt() = 0;
    virtual ~QInterruptable() = default;
};

#endif // QINTERRUPTABLE_H

ThreadWorker.h is the Object that lives (and runs) inside QWaitThread

#ifndef THREADWORKER_H
#define THREADWORKER_H

#include <QObject>
#include <functional>
#include <QWaitCondition>
#include <QMutex>

#include "QInterruptable.h"

class ThreadWorker : public QObject, public QInterruptable
{
    Q_OBJECT

private:
    QMutex mutex;
    QWaitCondition *waitCondition;
    std::function<void ()> runnable;
    bool shouldPause = false;

public:
    explicit ThreadWorker(QObject *parent = nullptr);
    ThreadWorker(std::function<void ()> func);
    ~ThreadWorker();

    void setRunnable(const std::function<void ()> &value);

signals:
    /**
     * Emitted when the QWorkerThread object has started work
     * @brief started
     */
    void started();

    /**
     * @brief progress reports on progress in method, user defined.
     * @param value reported using int
     */
    void progress(int value);

    /**
     * Emitted when the QWorkerThread object has finished its work, same signal is used from &QThread::finished
     * @brief started
     */
    void finished();

    /**
     * Emitted when the QWorkerThread has encountered an error, user defined.
     * @brief started
     */
    void error();

public slots:
    virtual void run();
    virtual void cleanup();


    // QInterruptable interface
public:
    void pause()
    {
        shouldPause = true;
    }
    void resume()
    {
        shouldPause = false;
    }
    QMutex& getMutex();
    QWaitCondition *getWaitCondition() const;
    void setWaitCondition(QWaitCondition *value);
    bool getShouldPause() const;

    // QInterruptable interface
public:
    void interrupt()
    {

    }
};

#endif // THREADWORKER_H

ThreadWorker.cpp

#include "threadworker.h"

void ThreadWorker::setRunnable(const std::function<void ()> &value)
{
    runnable = value;
}

QMutex& ThreadWorker::getMutex()
{
    return mutex;
}

QWaitCondition *ThreadWorker::getWaitCondition() const
{
    return waitCondition;
}

void ThreadWorker::setWaitCondition(QWaitCondition *value)
{
    waitCondition = value;
}

bool ThreadWorker::getShouldPause() const
{
    return shouldPause;
}

ThreadWorker::ThreadWorker(QObject *parent) : QObject(parent)
{
    waitCondition = new QWaitCondition;
}

ThreadWorker::ThreadWorker(std::function<void ()> func): runnable(func) {
    waitCondition = new QWaitCondition;
}


ThreadWorker::~ThreadWorker()
{    
    if(waitCondition != nullptr){
        delete waitCondition;
    }
}

void ThreadWorker::run()
{
    emit started();
    runnable();
    emit finished();
}

void ThreadWorker::cleanup()
{

}

QWorkerThread.h the main class of interest, where the runnable lambda is accepted and where the main 'thread' processing happens, moving to thread, starting thread, handling events, etc

#ifndef QWORKERTHREAD_H
#define QWORKERTHREAD_H

#include <QObject>
#include <functional>
#include <QThread>
#include <QEventLoop>

#include "qwaitthread.h"
#include "threadworker.h"

class QWorkerThread: public QObject
{
    Q_OBJECT

public:

    enum State {
        Running,
        Paused,
        NotRunning,
        Finished,
        Waiting,
        Exiting
    };

    QWorkerThread();
    explicit QWorkerThread(std::function<void ()> func);
    ~QWorkerThread();
    static QString parseState(QWorkerThread::State state);
    virtual void setRunnable(std::function <void()> runnable);
    virtual void start(QThread::Priority priority = QThread::Priority::InheritPriority);
    virtual void stop();
    virtual void wait(unsigned long time = ULONG_MAX);
    virtual void kill();
    virtual void setWorkerObject(ThreadWorker *value);

    virtual void pause();
    virtual void resume();
    virtual QWaitThread *getWorkerThread() const;

    State getState() const;


signals:
    /**
     * Emitted when the QWorkerThread object has started work
     * @brief started
     */
    void started();

    /**
     * @brief progress reports on progress in method, user defined.
     * @param value reported using int
     */
    void progress(int value);

    /**
     * Emitted when the QWorkerThread object has finished its work, same signal is used from &QThread::finished
     * @brief started
     */
    void finished();

    /**
     * Emitted when the QWorkerThread has encountered an error, user defined.
     * @brief started
     */
    void error();

private:
    /**
     * @brief workerObject - Contains the object and 'method' that will be moved to `workerThread`
     */
    ThreadWorker *workerObject = nullptr;

    /**
     * @brief workerThread - Worker Thread is seperate thread that runs the method
     */
    QWaitThread *workerThread = nullptr;

    State state = State::NotRunning;

};

#endif // QWORKERTHREAD_H

QWorkerThread.cpp implementation

#include "qworkerthread.h"

QWorkerThread::QWorkerThread()
{
    state = State::NotRunning;
    workerThread = new QWaitThread;
    workerObject = new ThreadWorker;
    workerThread->setObjectName("WorkerThread");
}

QWorkerThread::QWorkerThread(std::function<void ()> func)
{
    state = State::NotRunning;
    workerThread = new QWaitThread;
    workerObject = new ThreadWorker(func);
    workerThread->setObjectName("WorkerThread");
}

QWorkerThread::~QWorkerThread()
{
    //  Check if worker thread is running
    if(workerThread->isRunning()) {

        // Exit thread with -1
        workerThread->exit(-1);
    }

    if(!workerThread->isFinished()) {
        workerThread->wait(500);

        if(workerThread->isRunning()) {
            workerThread->terminate();
        }
    }

    // cleanup
    delete workerObject;
    delete workerThread;
}

void QWorkerThread::setRunnable(std::function<void ()> runnable)
{
    workerObject->setRunnable(runnable);
}

void QWorkerThread::start(QThread::Priority priority)
{

    state = State::Running;
    // Connect workerThread start signal to ThreadWorker object's run slot
    connect(workerThread, &QThread::started, workerObject, &ThreadWorker::started);
    connect(workerThread, &QThread::started, workerObject, &ThreadWorker::run);

    // Connect threadWorker progress report to this progress report
    connect(workerObject, &ThreadWorker::progress, this, &QWorkerThread::progress);

    // Cleanup
    connect(workerObject, &ThreadWorker::finished, this, [this](){
        state = State::Finished;
        emit finished();
    });
    connect(workerThread, &QWaitThread::finished, this, [this] {
        workerObject->deleteLater();
    });

    // move workerObject to thread
    workerObject->moveToThread(workerThread);

    // emit signal that we are starting
    emit started();

    // Start WorkerThread which invokes object to start process method
    workerThread->start(priority);
}

void QWorkerThread::stop()
{
    state = State::Exiting;
    // Exit thread safely with success
    workerThread->quit();

    emit finished();
}

void QWorkerThread::wait(unsigned long time)
{
    state = State::Waiting;
    workerThread->wait(time);
}

void QWorkerThread::kill()
{
    // try stopping
    stop();

    // check if still running
    if(workerThread->isRunning()){
        // forcefully kill
        workerThread->terminate();
        workerThread->wait();
    }

    emit finished();
}

void QWorkerThread::setWorkerObject(ThreadWorker *value)
{
    workerObject = value;
}

QWaitThread *QWorkerThread::getWorkerThread() const
{
    return workerThread;
}

QWorkerThread::State QWorkerThread::getState() const
{
    return state;
}

QString QWorkerThread::parseState(QWorkerThread::State state) {
    switch (state) {
        case Running:
            return "Running";
        case Paused:
            return "Paused";
        case NotRunning:
            return "NotRunning";
        case Finished:
            return "Finished";
        case Waiting:
            return "Waiting";
        case Exiting:
            return "Exiting";
    }

    return QString("Unknown State [%1]").arg(QString::number(state)) ;
}

void QWorkerThread::pause()
{
    workerObject->pause();
    state = State::Paused;
}

void QWorkerThread::resume()
{
    workerObject->resume();
    state = State::Running;
}

Update with Some extra info

Regarding the ~QWorkerThread() , I noticed that when calling delete QThread or QThread::deleteLater() , the QWaitThread() (or QThread ) will throw a Fatal error : Thread destroyed while it is still running. This is after quit() / terminate() was called.

The following line from QThread.cpp

if (d->running && !d->finished && !d->data->isAdopted)
    qFatal("QThread: Destroyed while thread is still running");

where

d->running == true
d->finished == false
d->data->isAdopted ?

I've tested your code and here is what I realized.
As you mentioned, terminate() doesn't completely stop a thread. Qt doc says:

Terminates the execution of the thread. The thread may or may not be terminated immediately, depending on the operating system's scheduling policies. Use QThread::wait() after terminate() , to be sure.

Unfortunately, the wait() freezes even after terminate() . It might be a problem with your code, but I created a maximally simplified example to verify this, which still has the same problems.

Firstly, here is the part of your code what I would suggest to change:

QWorkerThread::~QWorkerThread()
{
    ...
    // cleanup
    delete workerObject; // Unsafe, but the only way to call the destructor, if necessary
    delete workerThread; // qFatal
}

Here is what Qt doc says about the destructor unsafety:

Calling delete on a QObject from a thread other than the one that owns the object (or accessing the object in other ways) is unsafe , unless you guarantee that the object isn't processing events at that moment. Use QObject::deleteLater() instead, and a DeferredDelete event will be posted, which the event loop of the object's thread will eventually pick up. By default, the thread that owns a QObject is the thread that creates the QObject , but not after QObject::moveToThread() has been called.

Note. Changing delete workerThread to workerThread->deleteLater() works for me without qFatal .


Ok, what problems we actually have:

  1. Desctructor of QThread subclass cannot be called directly after terminate() due to qFatal
  2. wait() freezes and cannot be used after terminate() despite to docs

(seems the problem is actual only when an infinite operation is moved into event loop)

Make sure the problem is not in another place of your code Minimum reproducible example

Worker.h

#pragma once

#include <QObject>

class Worker : public QObject
{
    Q_OBJECT

public:
    ~Worker();

public slots:
    void process();
};

Worker.cpp

#include "Worker.h"
#include <QThread>
#include <QDebug>
#include <QDateTime>

Worker::~Worker()
{
    qDebug() << "~Worker()";
}

void Worker::process()
{
    qDebug("Hello World!");

    while(true)
    {
        qDebug() << QDateTime::currentDateTime();
        QThread::msleep(100);
    }
}

MainWin.h

#pragma once

#include <QtWidgets/QMainWindow>

class QThread;
class Worker;

class MainWin : public QMainWindow
{
    Q_OBJECT

public:
    MainWin(QWidget *parent = nullptr);
    ~MainWin();

private:
    QThread*    thread = nullptr;
    Worker*     worker = nullptr;
};

MainWin.cpp

#include "MainWin.h"
#include "Worker.h"
#include <QThread>
#include <QDebug>
#include <QDateTime>

MainWin::MainWin(QWidget *parent)
  : QMainWindow(parent)
{
    thread = new QThread;
    worker = new Worker;

    worker->moveToThread(thread);

    // Start only one infinite operation
    connect(thread, &QThread::started, worker, &Worker::process);

    thread->start();
}

MainWin::~MainWin()
{
    if (thread->isRunning())
    {
        thread->exit(-1);
        thread->wait(500);
    }

    if (thread->isRunning())
    {
        thread->terminate();
    }

    //cleanup
    delete worker;
    delete thread; // qFatal("QThread: Destroyed while thread is still running")
}

The only working code what I've found

MainWin::~MainWin()
{
    ...
    //cleanup
    delete worker; // Worker destructor will be called, but be note this is unsafe
    thread->deleteLater(); // Allows to avoid qFatal but make thread terminated
}

Conclusions and suggestions

Everything I can offer besides to avoid terminate() at all, is to use terminate() without wait() and then workerThread->deleteLater() .

If time-consuming operation which you're trying to terminate, is your own code, consider embedding some terminate flag into the code.

It would be better to avoid raw pointers and replace they by smart pointers, where possible.


What else I can offer as universal way to run lambdas in threads

Simplified example how can one use lamdas, signals-slots, threads, started-finished signals, QtConcurrent::run() and QFuture<> . This way you can achieve both running code in one persistent additional thread and inside an automatic thread pool as well. But termination is not supported.

LambdaThread.h

#pragma once

#include <QObject>
#include <functional>
#include <QFuture>

class QThreadPool;

class LambdaThread : public QObject
{
    Q_OBJECT

public:
    // maxThreadCount = -1 to use idealThreadCount by default
    LambdaThread(QObject *parent, int maxThreadCount = -1);

signals:
    void started();
    void finished();

public slots:
    // Invoke this directly or by a signal
    QFuture<void> setRunnable(std::function<void()> func);

private:
    /*
    For the case you need persistent thread sometimes.
    In the case you never need persistent thread,
    just remove m_threadPool from this class at all
    */
    QThreadPool* m_threadPool = nullptr;
};

LambdaThread.cpp

#include "LambdaThread.h"
#include <QtConcurrent/QtConcurrent>
#include <QThreadPool>

LambdaThread::LambdaThread(QObject *parent, int maxThreadCount /*= -1*/)
    : QObject(parent)
{
    m_threadPool = new QThreadPool(this);

    if(maxThreadCount > 0)
    {
        m_threadPool->setMaxThreadCount(maxThreadCount);

        if (maxThreadCount == 1)
        {
            // Avoid thread affinity changing
            m_threadPool->setExpiryTimeout(-1);
        }
    }
}

QFuture<void> LambdaThread::setRunnable(std::function<void()> func)
{
    return QtConcurrent::run(m_threadPool,
        [this, func]()
    {
        // Be note that you actually need event loop in a receiver thread only
        emit started();

        func();

        emit finished();
    });
}

Just GUI class example where you can start your runnables and receive signals.

MainWin.h

#pragma once

#include <QtWidgets/QMainWindow>
#include <functional>

class LambdaThread;

class MainWin : public QMainWindow
{
    Q_OBJECT

public:
    MainWin(QWidget *parent = nullptr);

signals:
    // For the case you want to use signals
    void newRunnable(std::function<void()> func);

private:
    LambdaThread* m_lambdaThread = nullptr;
};

MainWin.cpp

#include "MainWin.h"
#include "LambdaThread.h"
#include <QFuture>
#include <QDebug>

MainWin::MainWin(QWidget *parent)
    : QMainWindow(parent)
{
    m_lambdaThread = new LambdaThread(this);

    connect(this, &MainWin::newRunnable,
        m_lambdaThread, &LambdaThread::setRunnable);

    /*
    Do not forget the third (`this`) context variable
    while using modern signal-slot connection syntax with lambdas
    */
    connect(m_lambdaThread, &LambdaThread::started,
        this, []()
    {
        qDebug() << "Runnable stated";
    });

    connect(m_lambdaThread, &LambdaThread::finished,
        this, []()
    {
        qDebug() << "Runnable finished";
    });

    // Set your lambda directly
    QFuture<void> future = m_lambdaThread->setRunnable([]()
    {
        qDebug() << "hello from threaded runnable";
    });

    // You can also use future (not necessary of course)
    //future.waitForFinished();

    // Or you can emit your lambda via the signal:
    emit newRunnable([]()
    {
        qDebug() << "hello from threaded runnable which comes from signal";
    });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM