调用 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.h是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 接口定义了一些预期的功能
#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 QThread
或QThread::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->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->deleteLater()
。
如果您尝试终止的耗时操作是您自己的代码,请考虑在代码中嵌入一些终止标志。
最好避免使用原始指针,并在可能的情况下用智能指针替换它们。
我还能提供什么作为在线程中运行 lambda 的通用方法
如何使用 lamdas、信号槽、线程、开始-完成信号、QtConcurrent::run()
和 QFuture<>
的简化示例。通过这种方式,您既可以在一个持久的附加线程中运行代码,也可以在自动线程池中实现代码运行。但不支持终止。
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 线程执行不会停止的主要内容,如果未能解决你的问题,请参考以下文章
python:sys.exit() os._exit() exit() quit()