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
    原样只是有一个长期存在的错误。您不应该重新实现
    运行
    -这就是为什么它是最终的。您不能超越它de
    Thread::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")