调用 exit() quit() 或 terminate() 后 Qt 线程执行不会停止

Posted

技术标签:

【中文标题】调用 exit() quit() 或 terminate() 后 Qt 线程执行不会停止【英文标题】:Qt thread execution does not stop after exit() quit() or terminate() was called 【发布时间】:2020-07-11 08:29:19 【问题描述】:

首先,我已经阅读了QThreads 并使用了QEventLoop,但我并不完全确定我的实现是否正确。

TL;DR 请参阅下面的问题详情

最有用的信息来源是 Qt Wiki、KDAB Qthread 演示文稿(适用于 w/&w/o 事件循环)、与此问题相关的帖子 here 和 here。

我的情况是:

我有一个可能运行时间很长的函数,其中包含多个 I/O 磁盘调用。因此我需要一个线程来不阻塞 UI。为此,我自己实现了一个线程。

TL;DR QThreads

我的理解是 QThread 是一个单独的事件循环对象,需要自定义 run() 实现或将对象移动到新创建的线程 object 其中移动的对象( s) 生活(并运行)。我描述的是带有事件循环实现。

问题

我可能遗漏了一些东西,因为我在上面描述的这个实现不能正常工作。我怎么知道这一点,上面的 Qt Docs 和 SO 帖子提到 QThread::quit() 或 QThread::exit() 依赖于 QEventLoop,如果 QThread::exec() 没有运行(通过调用 QThread::run() 通过 QThread::start() ),那么 quit()exit() 函数将永远不会运行,这是我的问题

我的实现哲学类似于 Java 的 Thread & Lambda 语法,例如

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

我使用了以下实现

可以使用 lambda 的各种线程对象容器

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

简单示例用法将是(在QWorkerThread 内完成线程和子对象清理):

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

问题详情/示例

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

调用QThread::quit()QThread::quit(),然后QThread::terminate() 后跟QThread::wait() 不会终止线程。 lambda 中定义的长时间运行的进程(在setRunnable() 中)将一直运行到完成为止。

我知道这篇文章比传统的要长,但我希望每个人都能全面了解我正在努力实现的目标,因为我不确定我的问题到底出在哪里。

任何帮助将不胜感激!


代码实现

我将发布所有代码以全面了解实现,以防我错过重要的东西。

QWaitThread.hQThread的实现@

#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 接口定义了一些预期的功能

#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 是在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 感兴趣的主类,runnable lambda 被接受,主“线程”处理发生在哪里,移动到线程,启动线程,处理事件等

#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 实现

#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;


更新一些额外信息

关于~QWorkerThread(),我注意到在调用delete QThreadQThread::deleteLater() 时,QWaitThread()(或QThread)会抛出一个Fatal error:线程在运行时被销毁。这是在调用 quit()/terminate() 之后。

来自QThread.cpp的以下行

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

在哪里

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

【问题讨论】:

你的QWaitThread::run() 身体在哪里?为什么声明为public @VladimirBershov 我很抱歉,我忘了它——它不应该在那里。我在测试某些东西时添加了这个,我将删除它。我正在使用默认的QThread::run() 方法 您如何看待QtConcurrent::run(),它允许您在 QFuture 支持下运行您想要的任何东西? @VladimirBershov 它看起来只是 lambdas 的一个非常好的替代方案,但我还需要使用 Signals/Slots impl,而且看起来 QtConcurrent::run() 不允许这样做。为此,我使用自定义 impl 子类化 QWorkerThread,向标头添加信号,并在我的 lambda runnable 中发出这些信号(在我喜欢的任何地方定义)。 来自QThread::terminate() 的文档:终止线程的执行。线程可能会或可能不会立即终止,具体取决于操作系统的调度策略。 确定在 terminate() 之后使用 QThread::wait()。 您没有在析构函数中的 terminate() 之后使用 wait()。 【参考方案1】:

我已经测试了您的代码,这就是我所意识到的。 正如您所提到的,terminate() 并没有完全停止线程。 Qt 文档说:

终止线程的执行。线程可能会或可能不会立即终止,具体取决于操作系统的调度策略。 请务必在terminate() 之后使用QThread::wait()

不幸的是,wait() 即使在terminate() 之后也会冻结。这可能是您的代码有问题,但我创建了一个最大限度简化的示例来验证这一点,它仍然存在相同的问题。

首先,这是我建议更改的代码部分:

QWorkerThread::~QWorkerThread()

    ...
    // cleanup
    delete workerObject; // Unsafe, but the only way to call the destructor, if necessary
    delete workerThread; // qFatal

这是 Qt 文档关于析构函数不安全的说法:

从拥有对象(或以其他方式访问对象)之外的线程调用 QObject 上的 delete 是不安全的,除非您保证对象未在处理那一刻的事件。改用QObject::deleteLater(),将发布DeferredDelete 事件,对象线程的事件循环最终将拾取该事件。默认情况下,拥有QObject 的线程是创建QObject 的线程,但不是在调用QObject::moveToThread() 之后。

注意。delete workerThread 更改为workerThread-&gt;deleteLater() 对我有效,而无需qFatal


好的,我们实际遇到了什么问题:

    QThread 子类的析构函数不能在 terminate() 之后直接调用,因为 qFatal wait() 冻结,在 terminate() 之后无法使用,尽管有文档

(似乎问题只有在将无限操作移入事件循环时才是实际问题)

确保问题不在代码的其他地方 最小可重现示例

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")

我发现的唯一有效代码

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

结论和建议

除了完全避免terminate()之外,我所能提供的一切就是使用terminate()而不使用wait(),然后使用workerThread-&gt;deleteLater()

如果您尝试终止的耗时操作是您自己的代码,请考虑在代码中嵌入一些终止标志。

最好避免使用原始指针,并在可能的情况下用智能指针替换它们。


我还能提供什么作为在线程中运行 lambda 的通用方法

如何使用 lamdas、信号槽、线程、开始-完成信号、QtConcurrent::run()QFuture&lt;&gt; 的简化示例。通过这种方式,您既可以在一个持久的附加线程中运行代码,也可以在自动线程池中实现代码运行。但不支持终止。

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();
    );


只是 GUI 类示例,您可以在其中启动可运行文件并接收信号。

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";
    );

【讨论】:

非常感谢您抽出宝贵的时间来浏览本文并提供解决方案和替代方案。我会在一天左右的时间内详细查看它,但真的,谢谢!

以上是关于调用 exit() quit() 或 terminate() 后 Qt 线程执行不会停止的主要内容,如果未能解决你的问题,请参考以下文章

Halcon算子翻译——exit

python:sys.exit() os._exit() exit() quit()

在UBUNTU下,编写一个叫quit的命令,当在终端运行quit,则相当于运行exit,关闭当前终端。

在linux的终端怎么退出python命令行

qt的线程函数体结束意味着线程结束吗

python怎么上一行删不掉