C++ QThread将不会退出
当我关闭应用程序时,线程仍在运行,即使它本应结束。下面的代码将直接挂起C++ QThread将不会退出,c++,multithreading,qt,C++,Multithreading,Qt,当我关闭应用程序时,线程仍在运行,即使它本应结束。下面的代码将直接挂起workerThread->wait()行 主线程 #包括“mainwindow.h” #包括“ui_main window.h” 主窗口::主窗口(QWidget*父窗口): QMainWindow(父级), 用户界面(新用户界面::主窗口) { 用户界面->设置用户界面(此); workerThread=新的QThread(); 工人=新工人(); worker->moveToThread(workerThread); 连
workerThread->wait()代码>行
主线程
#包括“mainwindow.h”
#包括“ui_main window.h”
主窗口::主窗口(QWidget*父窗口):
QMainWindow(父级),
用户界面(新用户界面::主窗口)
{
用户界面->设置用户界面(此);
workerThread=新的QThread();
工人=新工人();
worker->moveToThread(workerThread);
连接(此,信号(ThreadStopSignal()),工作线程,插槽(ThreadStopSlot());
连接(此,信号(startWorksignal()),工作,插槽(RunSlot());
连接(worker、SIGNAL(finished())、workerThread、SLOT(quit());
连接(worker、信号(finished())、worker、插槽(deleteLater());
连接(workerThread,SIGNAL(finished()),workerThread,SLOT(deleteLater());
workerThread->start();
发射(startRokerSignal());
qDebug()问题相当简单:当等待线程完成时,会在主线程中阻塞事件循环。但是,同时,事件循环必须接收workerThread->quit()
调用。因此会出现死锁
简单的修复方法是在wait()
等待之前显式地quit()
线程。您可以通过修复QThread
的固有中断来实现这一点。有关thread
类的安全实现,请参见下文。然后您可以在main window
的析构函数中简单地销毁线程
唉,代码有一些反模式和巫毒
workerThread
和worker
都不应该是指针。这是过早的悲观看法,因为您添加了一个额外的堆分配和一个额外的间接层。这是毫无意义的,并迫使您在不需要的情况下执行手动内存管理
threadStopSlot
是线程安全的,没有理由从工作线程的事件循环调用它。您可以直接调用它。这样做时,您不需要在runSlot
中调用QCoreApplication::processEvents
。当您这样做时,您重新进入事件循环,突然所有对象都在运行在这种情况下,线程受可重入性要求的约束,因此必须进行审计
由于您可能希望让事件循环在工作线程中运行,因此应该反转控件:不要将控件保留在runSlot
中,而是将其保留在事件循环中,并让事件循环反复调用runSlot
。这就是零超时计时器的用法
<> >初始化列表产生习惯C++,使用它们。< /P>
emit
表示前缀,而不是函数。您可以emit fooSignal()
,而不是emit(fooSignal())
。这是风格的问题,没错,但是emit
是用于文档目的的。它只用于人类消费,我们人类阅读东西时不需要额外的一层括号。如果你不关心文档方面,就根本不要使用emit
。信号是机器的常规方法-生成的实现。您不需要以任何特殊方式调用它们
由于您使用Qt5,所以应该使用编译时检查的connect
语法。这不需要C++11编译器,除非您也使用lambdas
在窗口的析构函数中要求线程退出可能不是一个好主意,因为在工作线程退出时,在主线程中可能还有其他事情要做,这些事情需要运行事件循环
销毁窗口的唯一方法是,如果窗口具有WA_DeleteOnClose
属性,或者如果退出主事件循环并在退出main()时销毁窗口
。您应该捕获窗口的关闭事件,并将其设置为运动状态,以便只有当所有应该完成的事情都完成时,窗口才会被删除
您可以输出线程本身,而不是将线程id输出到qDebug()
。您可以利用线程是对象这一事实,为它们指定人类可读的名称。这样,您就不需要手动比较线程id或地址,只需读取线程的名称即可
考虑到以上所有因素,如果你让我写代码,我会按如下方式做
首先,工人功能可以抽象为工人基础:
#include <QtWidgets>
class WorkerBase : public QObject {
Q_OBJECT
Q_PROPERTY(bool active READ isActive WRITE setActive)
QBasicTimer m_runTimer;
bool m_active;
protected:
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_runTimer.timerId()) return;
work();
}
virtual void workStarted() {}
virtual void work() = 0;
virtual void workEnded() {}
public:
WorkerBase(QObject * parent = 0) : QObject(parent), m_active(false) {
setActive(true);
}
/// This method is thread-safe.
bool isActive() const { return m_active; }
/// This method is thread-safe.
void setActive(bool active) {
QObject source;
QObject::connect(&source, &QObject::destroyed, this, [this,active]{
// The functor is executed in the thread context of this object
if (m_active == active) return;
if (active) {
m_runTimer.start(0, this);
workStarted();
} else {
m_runTimer.stop();
workEnded();
}
m_active = active;
}, thread() ? Qt::QueuedConnection : Qt::DirectConnection);
}
~WorkerBase() {
Q_ASSERT(QThread::currentThread() == thread() || !thread());
setActive(false);
}
};
与qt 5.1.1一样,我也使用了不同于大多数人可能使用的编码标准。这超出了我的问题范围,但如果可能的话,我宁愿不将QThread子类化。考虑到QThread的工作方式,这通常是一种不好的做法,在这种情况下,会导致事情在稍后实现的地方出现问题“PDel<代码> qthox/CODE >被破坏。正常的C++对象随时都可以被销毁。默认情况下,QThread不能总是这样。所以你必须修复它。这就是你如何正确地实现C++ RAII模式。<代码> QTox没有正确地实现它,而你必须通过明确地调用<代码> CUTE()/<代码>和<代码> WAIT()来解决它。
,这是一个隐藏的炸弹,一旦您忘记在某些代码路径中执行该操作,它就会爆炸。@pdel我展示的子类化不仅是良好的实践,而且是绝对必要的,QThread
原样只是有一个长期存在的错误。您不应该重新实现运行
-这就是为什么它是最终的。您不能超越它deThread::run
,即使您想运行也可以。如果您希望将继承的实现标记为final,您所看到的是最后一个习惯用法。
class Worker : public WorkerBase {
Q_OBJECT
int m_runCount;
protected:
void workStarted() Q_DECL_OVERRIDE {
qDebug() << "Starting" << QThread::currentThread();
}
void work() Q_DECL_OVERRIDE {
QThread::msleep(1000);
++ m_runCount;
qDebug() << m_runCount << QThread::currentThread();
}
void workEnded() Q_DECL_OVERRIDE {
qDebug() << "Finishing" << QThread::currentThread();
emit finished();
}
public:
Worker(QObject * parent = 0) : WorkerBase(parent), m_runCount(0) {}
Q_SIGNAL void finished();
};
class Thread : public QThread {
using QThread::run; // final method
public:
Thread(QObject * parent = 0) : QThread(parent) {}
~Thread() { quit(); wait(); }
};
class MainWindow : public QMainWindow {
Q_OBJECT
Worker m_worker;
Thread m_workerThread;
QLabel m_label;
protected:
void closeEvent(QCloseEvent * ev) {
if (m_worker.isActive()) {
m_worker.setActive(false);
ev->ignore();
} else
ev->accept();
}
public:
MainWindow(QWidget * parent = 0) : QMainWindow(parent),
m_label("Hello :)\nClose the window to quit.")
{
setCentralWidget(&m_label);
m_workerThread.setObjectName("m_worker");
m_worker.moveToThread(&m_workerThread);
connect(&m_worker, &Worker::finished, this, &QWidget::close);
m_workerThread.start();
qDebug() << "Main thread:" << QThread::currentThread();
}
~MainWindow() {
qDebug() << __FUNCTION__ << QThread::currentThread();
}
};
int main(int argc, char ** argv)
{
QApplication a(argc, argv);
QThread::currentThread()->setObjectName("main");
MainWindow w;
w.show();
w.setAttribute(Qt::WA_QuitOnClose);
return a.exec();
}
#include "main.moc"
Main thread: QThread(0x7fa59b501180, name = "main")
Starting QThread(0x7fff5e053af8, name = "m_worker")
1 QThread(0x7fff5e053af8, name = "m_worker")
2 QThread(0x7fff5e053af8, name = "m_worker")
3 QThread(0x7fff5e053af8, name = "m_worker")
Finishing QThread(0x7fff5e053af8, name = "m_worker")
~MainWindow QThread(0x7fa59b501180, name = "main")