6

在下面的示例中(在 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. 是程序员的错误,而不是wait()(最终)调用 QThread 吗?
    • 即使是这样,我也尝试wait()在一个插槽内,aboutToQuit()并且在长时间运行操作完成后应用程序没有关闭(使用如上所述的设置)。只有quit(); wait();(在提到的插槽内)允许应用程序关闭。为什么?
4

2 回答 2

7

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

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

下面是QThread. 这是最后一堂课,因为您无法重新实现run. 这很重要,因为 run 的重新实现可以以quit一种无操作的方式完成,从而破坏了 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,线程退出者将quit()所有工作线程。这允许线程并行结束。然后,thread2.~Thread将等待该线程完成,然后thread1.~Thread将执行相同的操作。线程现在消失了,对象是无线程的并且可以安全地销毁:worker2.~QObject首先调用,然后是worker1.~QObject.

于 2013-10-29T18:27:42.153 回答
2

1)是父母/孩子的问题吗?

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

2)不调用wait()是程序员的错误吗?

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

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

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

于 2013-10-29T11:28:07.657 回答