QThread - 如何阻止工人?

时间:2013-03-07 10:43:56

标签: c++ qt

我正在读取我应该使用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完成时中断循环但是不知道如何在worker对象中执行它。我可以在worker对象中创建一些标志并从主线程切换它吗?或者我可以找到线程所有者并检查它的状态吗?或者在启动线程之前更好,在工作对象中设置线程指针,然后检查状态?什么是最好的线程安全解决方案?

此致

4 个答案:

答案 0 :(得分:6)

如果有任何正在运行的话,在线程对象上调用quit()或exit()将简单地结束线程的事件循环。但正如你正确指出的那样,原来的问题仍然存在。如果worker函数已经由事件循环执行并且是具有forever构造的长期运行函数,该怎么办?对quit()或exit()的调用只会等到worker函数返回。

除了为调用者提供公共函数之外,还可以建议一些方法,这将改变内部标志。

  • 在您的工人阶级中提供终止信号和插槽。事如下。

    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文档中提供的以下代码段。

void long_task() {
    forever {
        if ( QThread::currentThread()->isInterruptionRequested() ) {
            return;
        }
    }
}

你也可以在这里直接调用quit()来结束线程的事件循环。

答案 1 :(得分:3)

编辑:对不起,我误解了你的问题。

有几种选择。您可以创建一些标志,并在处理循环的每次迭代之前检查是否已设置标志。或者,在列表/队列中处理数据的情况下,您可以通过特殊的数据结束元素来指示进程终止。

答案 2 :(得分:1)

我和Dibo有同样的问题。我在doWork()函数中运行了一个很长的计算循环,需要能够从我的主线程中停止它。

查德克·罗伯特的答案并没有为我做到这一点。第一个建议就像他描述的成员函数一样。也就是说,虽然我们使用信号和插槽,但是从主线程中发出的信号调用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
}

正如您所看到的,使用此解决方案,循环将仅以&#39; 10000000&#39;的整数倍数中断。这可能不适合某些用例。

如果有人知道这个杀手解决方案,我很乐意听。

答案 3 :(得分:0)

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宏。