这是一个如何正确使用 QThread 的示例,但它存在一些问题,这些问题反映在评论中。特别是,由于插槽的执行顺序没有严格定义,因此可能会导致各种问题。2013 年 8 月 6 日发布的评论很好地说明了如何处理此问题。我在我的程序中使用了类似的东西,这里有一些示例代码需要澄清。
基本思想是相同的:我创建一个位于主线程中的 QThread 实例,一个位于我创建的新线程中的工作类实例,然后连接所有信号。
void ChildProcesses::start()
{
QThread *childrenWatcherThread = new QThread();
ChildrenWatcher *childrenWatcher = new ChildrenWatcher();
childrenWatcher->moveToThread(childrenWatcherThread);
// These three signals carry the "outcome" of the worker job.
connect(childrenWatcher, SIGNAL(exited(int, int)),
SLOT(onChildExited(int, int)));
connect(childrenWatcher, SIGNAL(signalled(int, int)),
SLOT(onChildSignalled(int, int)));
connect(childrenWatcher, SIGNAL(stateChanged(int)),
SLOT(onChildStateChanged(int)));
// Make the watcher watch when the thread starts:
connect(childrenWatcherThread, SIGNAL(started()),
childrenWatcher, SLOT(watch()));
// Make the watcher set its 'stop' flag when we're done.
// This is performed while the watch() method is still running,
// so we need to execute it concurrently from this thread,
// hence the Qt::DirectConnection. The stop() method is thread-safe
// (uses a mutex to set the flag).
connect(this, SIGNAL(stopped()),
childrenWatcher, SLOT(stop()), Qt::DirectConnection);
// Make the thread quit when the watcher self-destructs:
connect(childrenWatcher, SIGNAL(destroyed()),
childrenWatcherThread, SLOT(quit()));
// Make the thread self-destruct when it finishes,
// or rather, make the main thread delete it:
connect(childrenWatcherThread, SIGNAL(finished()),
childrenWatcherThread, SLOT(deleteLater()));
childrenWatcherThread->start();
}
一些背景:
ChildProcesses 类是一个子进程管理器,它使用 spawn() 调用启动新的子进程,保持当前运行的进程列表等等。但是,它需要跟踪子状态,这意味着在 Linux 上使用 waitpid() 调用或在 Windows 上使用 WaitForMultipleObjects。我曾经使用计时器在非阻塞模式下调用它们,但现在我想要更快速的反应,这意味着阻塞模式。这就是线程进来的地方。
ChildrenWatcher 类定义如下:
class ChildrenWatcher: public QObject {
Q_OBJECT
private:
QMutex mutex;
bool stopped;
bool isStopped();
public:
ChildrenWatcher();
public slots:
/// This is the method which runs in the thread.
void watch();
/// Sets the stop flag.
void stop();
signals:
/// A child process exited normally.
void exited(int ospid, int code);
/// A child process crashed (Unix only).
void signalled(int ospid, int signal);
/// Something happened to a child (Unix only).
void stateChanged(int ospid);
};
这里是如何工作的。当所有这些东西启动时,就会调用 ChildProcess::start() 方法(见上文)。它创建一个新的 QThread 和一个新的 ChildrenWatcher,然后将其移至新线程。然后我连接三个信号,这些信号通知我的经理其子进程的命运(退出/信号/上帝知道发生了什么)。然后开始主要的乐趣。
我将 QThread::started() 连接到 ChildrenWatcher::watch() 方法,以便在线程准备好后立即启动它。由于观察者生活在新线程中,因此执行 watch() 方法(队列连接用于调用插槽)。
然后我使用 Qt::DirectConnection 将 ChildProcesses::stopped() 信号连接到 ChildrenWatcher::stop() 插槽,因为我需要异步执行此操作。这是必需的,因此当不再需要 ChildProcesses 管理器时,我的线程会停止。stop() 方法如下所示:
void ChildrenWatcher::stop()
{
mutex.lock();
stopped = true;
mutex.unlock();
}
然后是 ChildrenWatcher::watch():
void ChildrenWatcher::watch()
{
while (!isStopped()) {
// Blocking waitpid() call here.
// Maybe emit one of the three informational signals here too.
}
// Self-destruct now!
deleteLater();
}
哦,isStopped() 方法只是在 while() 条件中使用互斥锁的便捷方式:
bool ChildrenWatcher::isStopped()
{
bool stopped;
mutex.lock();
stopped = this->stopped;
mutex.unlock();
return stopped;
}
所以这里发生的是我在需要完成时设置了停止标志,然后在下一次调用 isStopped() 时它返回 false 并且线程结束。
那么当 watch() 循环结束时会发生什么?它调用 deleteLater(),因此一旦控制权返回到线程事件循环,对象就会自毁,这发生在 deleteLater() 调用之后(当 watch() 返回时)。回到 ChildProcesses::start(),可以看到观察者的destroy() 信号与线程的quit() 槽之间存在连接。这意味着当观察者完成时线程会自动结束。当它完成时,它也会自毁,因为它自己的finished() 信号连接到它的deleteLater() 槽。
这与 Maya 发布的想法几乎相同,但因为我使用自毁习语,所以我不需要依赖调用插槽的顺序。它总是先自毁,然后停止线程,然后它也自毁。我可以在worker中定义一个finished()信号,然后将它连接到它自己的deleteLater(),但这只会意味着多了一个连接。由于我不需要为任何其他目的使用finished() 信号,因此我选择只从worker 本身调用deleteLater()。
Maya 还提到您不应该在 worker 的构造函数中分配新的 QObject,因为它们不会存在于您将 worker 移动到的线程中。我会说无论如何都要这样做,因为这就是 OOP 的工作方式。只需确保所有这些 QObject 都是 worker 的子对象(即使用 QObject(QObject*) 构造函数) - moveToThread() 将所有子对象与被移动的对象一起移动。如果您确实需要拥有不是对象子级的 QObject,则在您的工作人员中覆盖 moveToThread() 以便它也移动所有必要的东西。