![](/img/trans.png)
[英]Stop Qt Thread : calling exit() or quit() does not stop the thread execution
[英]Qt thread execution does not stop after exit() quit() or terminate() was called
首先,我已經閱讀了QThread並使用了QEventLoop,但我並不完全確定我的實現是正確的。
TL;DR 請參閱下面的問題詳細信息。
最有用的信息來源是Qt Wiki 、 KDAB Qthread演示(對 w/ & w/o 事件循環很有用)、與此問題相關的 SO 帖子here和here 。
我的場景是:
我有一個可能運行很長時間的函數,其中包含多個 I/O 磁盤調用。 因此我需要一個線程來不阻塞 UI。 為此,我自己實現了一個線程。
TL;DR QThreads
我的理解是QThread是一個單獨的事件循環對象,需要自定義run()實現或將對象移動到新創建的線程對象,其中移動的對象存在(和運行)。 我所描述的是帶事件循環實現的。
問題
我可能會遺漏一些東西,因為我的實現,我上面描述的,不能正常工作。 我怎么知道這一點,以及上面的 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中生存(和運行)的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
線程工作者.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 感興趣的主要類,其中接受可運行的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
)將拋出一個致命錯誤:線程在仍在運行時被破壞。 這是在調用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 ?
我已經測試了你的代碼,這是我意識到的。
正如您所提到的, terminate()
不會完全停止線程。 Qt 文檔說:
終止線程的執行。 線程可能會也可能不會立即終止,這取決於操作系統的調度策略。 可以肯定的是,在
terminate()
QThread::wait()
之后使用QThread::wait()
。
不幸的是,即使在terminate()
之后, wait()
也會凍結。 您的代碼可能有問題,但我創建了一個最大程度簡化的示例來驗證這一點,它仍然存在相同的問題。
首先,這是我建議更改的代碼部分:
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()
被調用。
筆記。 在沒有qFatal
情況下,將delete workerThread
更改為workerThread->deleteLater()
對我qFatal
。
terminate()
之后不能直接調用QThread
子類的qFatal
wait()
凍結並且在terminate()
之后無法使用(似乎只有在將無限操作移入事件循環時才實際出現問題)
工人.h
#pragma once
#include <QObject>
class Worker : public QObject
{
Q_OBJECT
public:
~Worker();
public slots:
void process();
};
工人.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);
}
}
主Win.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;
};
主Win.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()
。
如果您嘗試終止的耗時操作是您自己的代碼,請考慮在代碼中嵌入一些終止標志。
在可能的情況下,最好避免使用原始指針並用智能指針替換它們。
簡化示例如何使用 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 類示例,您可以在其中啟動可運行程序並接收信號。
主Win.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;
};
主Win.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";
});
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.