QThread - 如何停止工人?
Posted
技术标签:
【中文标题】QThread - 如何停止工人?【英文标题】:QThread - how to stop worker? 【发布时间】:2013-03-07 10:43:56 【问题描述】:我正在阅读我应该使用 worker 对象并通过 moveToThread 将其移动到线程,而不是直接从 QThread 继承。但是我找不到如何在我的对象工作者中停止循环的解决方案。例如我有测试循环:
void CollectionWorker::doWork()
for (int i = 0; i < 10; ++i)
sleep(1);
emit ping(i);
现在我将这个对象移动到线程:
worker->moveToThread(mTh);
这工作正常。但是当我调用 mTh.quit() 时,线程正在等待,直到 doWork 中的循环结束。当我直接从 QThread 继承时,我可以在每个循环上检查线程状态并在 thred 完成时中断循环,但不知道如何在工作对象中执行此操作。我可以在工作对象中创建一些标志并从主线程切换它吗?或者也许我可以找到线程所有者并检查它的状态?或者在启动线程之前更好,在工作对象中设置线程指针然后检查状态?什么是最好的线程安全解决方案?
问候
【问题讨论】:
【参考方案1】:在线程对象上调用 quit() 或 exit() 将简单地结束线程的事件循环(如果有任何正在运行)。但是正如您正确指出的那样,最初的问题仍然存在。如果工作函数已经被事件循环执行并且是一个具有永久构造的长时间运行函数怎么办。对 quit() 或 exit() 的调用将简单地等待工作函数返回。
除了使调用者可以使用公共函数以更改内部标志之外,还可以建议几种方法。
在你的工人阶级中给出一个终止信号和槽。如下所示。
signals:
void signalTermination();
public slots:
void setTerminationFlag();
private:
QMutex mutex;
bool terminationRequested;
您的广告位看起来像什么东西。
void setTerminationFlag()
QMutexLocker locker(&mutex);
terminationRequested = true;
然后您可以在永远循环的每次迭代中检查 doWork 函数中的变量。
mutex.lock();
if(terminationRequested)
//break from loop and effectively doWork function
mutex.unlock();
使用信号和槽而不是普通成员函数的原因之一是,如果您的工作函数在同步代码块内执行一些长时间运行的任务,则公共函数将保持阻塞状态,直到它访问同步对象。如果您的公共终止方法的调用线程是 UI 线程,这可能会产生不利影响。
如果您使用 Qt 5.2 及更高版本,另一种简洁且更简单的方法使用 QThread 的 requestInterruption() 方法。此方法在线程对象中设置一个咨询标志,您可以通过 doWork() 函数中的 isInterruptionRequested() 函数调用来检查它。请参阅QThread::isInterruptionRequested 文档中给出的以下代码 sn-p。
void long_task()
forever
if ( QThread::currentThread()->isInterruptionRequested() )
return;
这里也可以直接调用quit()来结束线程的事件循环。
【讨论】:
您的第一个建议似乎对我不起作用。尽管我们使用了信号,但setTerminationFlag()
函数仅在长循环结束后执行,正如您为成员函数所描述的那样。第二个选项,isInterruptionRequested()
,如果不是因为如果你以后想用它做一些类似的工作,你不能重置这个标志,那会很好。【参考方案2】:
编辑:对不起,我误解了你的问题。
有几种选择。您可以创建一些标志,并在处理循环的每次迭代之前检查标志是否已设置。或者,如果您在列表/队列中处理数据,您可能可以用特殊的数据结束元素发出终止进程的信号。
【讨论】:
是的,我知道,但这不是问题所在。如果我的循环是“永远”的怎么办。我想用类似的东西来停止它: for (int i = 0; i ) return;睡眠(1);发出 ping(i); 标志最有意义。while (stillDoingWork && notStopped)
从主线程(例如从按钮单击)线程中设置标志是否安全?只有主线程写入这个标志,worker 只会读取
@Dibo:您可以使用互斥锁或声明您的可能声明您的标志为易失性。【参考方案3】:
我和 Dibo 有同样的问题。我在我的 doWork()
函数中运行了一个很长的计算循环,并且需要能够从我的主线程中停止它。
Chadick Robbert 的回答对我没有用。第一个建议的行为就像他为成员函数所描述的那样。也就是说,虽然我们使用了信号和槽,但从主线程中发出的信号调用setTerminationFlag
槽,正确连接到它,只有在循环结束后才会执行。但也许我在实现上做错了。如果它真的应该起作用,请告诉我,因为这似乎是交易破坏者。
他使用QThread::currentThread()->isInterruptionRequested()
告诉线程停止的第二种选择会很好用循环,如果不一样。当然,您可以通过停止和启动线程来完成它,但我不确定它是否不会产生诸如清除执行队列之类的不利影响。这个问题实际上是作为bug report 发布的,Thiago Macieira(Qt 的主要开发人员)在那里提到,如果我可以引用他的话:
requestInterruption 函数的目的是结束线程。
这使得requestInterruption
不适合这项工作,因为它仅在线程开始和结束时重置。
我发现的一个对我来说似乎不是很干净的解决方案是将QCoreApplication
包含在工人类中并不时调用QCoreApplicaton::processEvents()
,以处理Chadick Robbert 的第一个建议中的那些排队信号或更新工人对线程之间共享的标志变量的类意识。
因为在你的循环中调用QCoreApplicaton::processEvents()
会显着减慢它,我所做的是这样的:
for(unsigned long int i=0; i<800000000 && !*stop; i++)
f += (double)i * .001; // dummy calculation
if(i%10000000 == 0) // Call it only from time to time
QCoreApplication::processEvents(); // update *stop according to main Thread
如您所见,使用此解决方案,循环只会在“10000000”的整数倍处中断,这对于某些用例可能不够用。
如果有人知道解决这个问题的杀手锏,我很想听听。
【讨论】:
是的。第一个选项应该以这种方式工作。请注意我的回答中的陈述“然后您可以在永远循环的每次迭代中检查doWork函数中的变量。” 这里的重点是,如果我们想优雅地完成线程,您希望线程至少完成其当前的循环执行。除非您的代码拥有消息泵并且您在执行每个语句之前检查它,否则没有更简洁的方法可以在循环的当前行中获取中断。正如您在自己的回答中指出的那样,即使您的解决方案也只能以 10000000 的倍数工作,但如果我想在计数 12345678 处结束线程,则不会。 因此,如果它是一个非常长的循环,请在步骤中创建逻辑检查点,然后检查标志或调用函数isInturruptionRequested()
。同样,我只是建议以更简洁的方式结束线程。
我确实尝试在每次迭代中检查它。那我一定是做错了什么。稍后再试。感谢您的回答。【参考方案4】:
Qt 中销毁工作线程的惯用方式是使用信号/槽接口:
CollectionWorker *worker = new CollectionWorker;
QThread *workerThread = new QThread(this);
connect(workerThread, SIGNAL(started()), worker, SLOT(doWork()));
connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
worker->moveToThread(workerThread);
为了使其工作,CollectionWorker 必须从 QObject 类继承并声明 Q_OBJECT 宏。
【讨论】:
这无助于停止工人。工作队列仍将完成。以上是关于QThread - 如何停止工人?的主要内容,如果未能解决你的问题,请参考以下文章