2

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

问候

4

4 回答 4

7

在线程对象上调用 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文档中给出的以下代码片段。

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

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

于 2015-06-20T22:28:48.747 回答
3

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

有几种选择。您可以创建一些标志,并在处理循环的每次迭代之前检查标志是否已设置。或者,如果您在列表/队列中处理数据,您可能可以用特殊的数据结束元素发出终止进程的信号。

于 2013-03-07T11:05:50.927 回答
1

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

Chadick Robbert 的回答对我没有用。第一个建议的行为就像他为成员函数所描述的那样。也就是说,虽然我们使用了信号和槽,setTerminationFlag但从主线程中发出的信号调用槽,正确连接到它,只有在循环结束后才会执行。但也许我在实现上做错了。如果它真的应该起作用,请告诉我,因为这似乎是交易破坏者。

QThread::currentThread()->isInterruptionRequested()如果不是因为如果您计划将线程和工作程序重用于某些类似的处理密集型循环(如果不是相同),那么您无法重置此标志,那么他使用告诉线程停止的第二种选择会很好用. 当然,您可以通过停止和启动线程来完成它,但我不确定它是否不会产生诸如清除执行队列之类的不利影响。这个问题实际上是作为错误报告发布的,如果我可以引用他的话,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”的整数倍处中断,这对于某些用例可能不够用。

如果有人知道对此的杀手级解决方案,我很想听听。

于 2016-08-17T09:30:30.460 回答
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 宏。

于 2013-03-08T21:09:36.197 回答