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

sth*_*m58 6 c++ qt multithreading worker qthread

在下面的示例中(在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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

问题出现了:

  1. 为什么立即workerThread擦拭?
    • 这是一些父母/子女问题吗?
    • Qt如何处理这种情况?
  2. 这是程序员的错误,而不是调用wait()QThread(最终)?
    • 即使这样,我也试着wait()进入一个插槽,aboutToQuit()并且在长时间运行操作完成后应用程序没有关闭(如上所述设置).仅quit(); wait();(在提到的插槽内)允许应用程序关闭.为什么?

Rei*_*ica 6

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

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

下面是一个安全的包装QThread.这是最后一堂课,因为你无法重新实现run.这很重要,因为可以通过这样的方式重新实现运行,从而使得quit无操作,破坏了类合同.

#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();
   }
};
Run Code Online (Sandbox Code Playgroud)

它可以使用如下:

#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();
}
Run Code Online (Sandbox Code Playgroud)

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