如果没有正确的wait()调用关闭应用程序,QThread会发生什么?

时间:2013-10-29 11:04:09

标签: c++ multithreading qt qthread worker

在下面的示例中(在Qt GUI应用程序中)启动了一个新线程(带有一个事件循环,我希望在其中完成一些工作):

void doWork()
{
   QThread* workerThread = new QThread();

   Worker* worker = new Worker();
   worker->moveToThread(workerThread);

   connect(workerThread, SIGNAL(started()), worker, SLOT(startWork()));
   connect(worker, SIGNAL(finished()), workerThread, SLOT(quit()));

   connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
   connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));

   workerThread->start();
}

startWork()可以是一个长时间运行的操作,在此期间可以关闭应用程序。

我希望只要在startWork()上执行workerThread,就不会关闭该应用程序。但是,当我关闭最后一个应用程序窗口时,workerThread即时消失(在长时间运行期间)并且应用程序关闭没有问题。

问题出现了:

  1. 为什么workerThread立刻被擦掉了?
    • 是否有父/子问题?
    • Qt如何处理这种情况?
  2. 程序员是错误的,不是在QThread上调用wait()(最终)?
    • 即使如此,我在wait()的插槽内尝试aboutToQuit(),并且在长时间运行操作完成后应用程序未关闭(如上所述设置)。只有quit(); wait();(在提到的插槽内)允许应用程序关闭。为什么?

2 个答案:

答案 0 :(得分:6)

QThread基本上是一个长期存在的API错误:它并不总是处于可破坏状态。在C ++中,当一个对象可以安全地调用它的析构函数时,它被认为是可破坏的状态。破坏正在运行的QThread是一个错误。 QThread只是一个线程控制器,它不是“线程”本身。想想QFile如何行动:你可以随时破坏它,无论它是否开放。它真正将文件的概念封装为资源。 QThread太弱了本机(系统)线程的包装器:当你破坏它时,它不会终止也不会处理本机线程(如果有的话)。这是资源泄漏(线程是操作系统资源),人们一遍又一遍地绊倒这个问题。

当应用程序的main()函数返回时,您的C / C ++运行时库的实现恰好终止了所有应用程序的线程,从而有效地终止了整个应用程序。这是否是您想要的行为取决于您。你应该quit()wait()你的事件循环运行线程。对于没有事件循环的线程,quit()是无操作,您必须实现自己的退出标志。在破坏之前,必须 wait()在线程上。这是为了防止竞争。

以下是QThread的安全包装。这是最后一堂课,因为你无法重新实现run。这很重要,因为可以通过以下方式重新实现运行,使quit成为无操作,打破contract of the class

#include <QThread>
#include <QPointer>

class Thread : public QThread {
   using QThread::run; // final
public:
   Thread(QObject * parent = 0) : QThread(parent) {}
   ~Thread() { quit(); wait(); }
};

class ThreadQuitter {
public:
   typedef QList<QPointer<Thread>> List;
private:
   List m_threads;
   Q_DISABLE_COPY(ThreadQuitter)
public:
   ThreadQuitter() {}
   ThreadQuitter(const List & threads) : m_threads(threads) {}
   ThreadQuitter(List && threads) : m_threads(std::move(threads)) {}
   ThreadQuitter & operator<<(Thread* thread) { 
     m_threads << thread; return *this;
   }
   ThreadQuitter & operator<<(Thread& thread) {
     m_threads << &thread; return *this;
   }
   ~ThreadQuitter() {
      foreach(Thread* thread, m_threads) thread->quit();
   }
};

可以按如下方式使用:

#include <QCoreApplication>

int main(int argc, char ** argv) {
   QCoreApplication app(argc, argv);
   QObject worker1, worker2;
   Thread thread1, thread2;
   // Style 1
   ThreadQuitter quitter;
   quitter << thread1 << thread2;
   // Style 2
   ThreadQuitter quitterB(ThreadQuitter::List() << &thread1 << &thread2);
   //
   worker1.moveToThread(&thread1);
   worker2.moveToThread(&thread2);
   thread1.start();
   thread2.start();

   QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection);
   return app.exec();
}

main返回后,线程quitter将quit()所有工作线程。这允许螺纹平行地向下卷绕。然后,thread2.~Thread将等待该线程完成,然后thread1.~Thread将执行相同的操作。线程现已消失,对象无线程且可以安全地被破坏:首先调用worker2.~QObject,然后调用worker1.~QObject

答案 1 :(得分:1)

1)是父母/子女问题吗?

不是你的代码 - 你没有为QThread做父母。如果你正在运行其他线程,Qt不喜欢你只是终止主线程。当应用程序终止时,您可能会在标准输出中看到它抱怨另一个线程仍在运行。但是,Qt会杀死另一个线程,这就是为什么有一个函数可以调用并等待线程正确终止。

2)程序员错误地不打电话给wait()吗?

是。如果您遇到线程没有正确退出的问题,那是因为您没有正确处理它,在这种情况下您可以打开另一个问题并显示关于您在退出前如何处理等待的代码。

  

当我关闭最后一个应用程序窗口时,workerThread立即消失

请注意,QApplication中有一个名为setQuitOnLastWindowClosed的函数,您可以将其设置为false,以防止应用程序在关闭最后一个窗口时自动退出。