从另一个线程中断循环

时间:2018-11-24 11:47:48

标签: c++ qt loops break qthread

当前我有两个看起来像这样的类:

class Worker : public QObject
{
    Q_OBJECT

    bool aborted = false;

public:
    Worker() : QObject() {}

public slots:
    void abort() { aborted = true; }

    void doWork()
    {
        while(!aborted && !work_finished)
        {
            //do work
            QCoreApplication::processEvents();
        }
    }
};

class Controller : public QObject
{
    Q_OBJECT

    QThread workerThread;
public:
    Controller() : QObject()
    {
        Worker *worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &Worker::deleteLater);
        connect(this, &Controller::startWork, worker, &Worker::doWork);
        connect(this, &Controller::aborted, worker, &Worker::abort);
    }

signals:
    void startWork();
    void aborted();
};

Controller *cont = new Controller;
emit cont->startWork(); // Start the loop
emit cont->aborted(); // Stop the loop

因此,我们的想法是在Worker线程中运行一个循环,可以从Controller线程中停止该循环。

在示例中,这是通过调用QCoreApplication::processEvents()来完成的,它允许信号在将控制权返回到循环之前调用插槽。
重要的是,循环只能在迭代的开始或结束时停止。

尽管这很好用,但我认为QCoreApplication::processEvents()相当昂贵,至少在很长的循环中使用(实际上多达数千个)。

所以我的问题是,如何才能以更好/更便宜的方式获得相同的结果?

1 个答案:

答案 0 :(得分:1)

我目前知道三种替代解决方案。

1。 QThread::requestInterruption(由@Felix建议)

根据QThread::isInterruptionRequested

  

请注意不要经常调用它,以保持较低的开销。

QCoreApplication::processEvents并没有对性能或内存使用情况做任何评论,因此我认为QThread::requestInterruption在这种情况下并不比QCoreApplication::processEvents有所改进。


2。 std::atomic(由@Felix建议)

  

原子对象的主要特征是从不同线程访问此包含的值不会导致数据争用[...]

布尔值可以存储在std::atomic类中,而可以成为Controller类的成员而不是Worker类的成员。然后,我们需要将对aborted的引用传递到Worker并将其存储在true中,并在需要时将其从Controller设置为class Worker : public QObject { Q_OBJECT std::atomic<bool> &aborted; public: Worker(std::atomic<bool> &aborted) : QObject(), aborted(aborted) {} public slots: void doWork() { while(!aborted.load() && !work_finished) /* do work */ } }; class Controller : public QObject { Q_OBJECT QThread workerThread; std::atomic<bool> aborted; public: Controller() : QObject() { aborted.store(false); Worker *worker = new Worker(aborted); worker->moveToThread(&workerThread); connect(&workerThread, &QThread::finished, worker, &Worker::deleteLater); connect(this, &Controller::startWork, worker, &Worker::doWork); connect(this, &Controller::aborted, worker, &Worker::abort); } void abort() { aborted.store(true); } signals: void startWork(); }; Controller *cont = new Controller; emit cont->startWork(); // Start the loop cont->abort(); // Stop the loop

我没有完全测试这种方法,所以如果我做错了,请纠正我。

paused

3。 QWaitConditionQMutex

将需要布尔值ControllerWorkerpaused需要对其具有读/写访问权限。

在需要时将true中的Controller设置为Worker
if(paused)中的循环期间,pausedQWaitCondition::wait()直到从调用线程中调用QWaitCondition::wakeAll()
每当访问class Worker : public QObject { Q_OBJECT bool &aborted, &paused; QWaitCondition &waitCond; QMutex &mutex; public: Worker(bool &aborted, bool &paused, QWaitCondition &waitCond, QQMutex &mutex) : QObject(), aborted(aborted), paused(paused), waitCond(waitCond), mutex(mutex) {} public slots: void doWork() { while(!aborted && !work_finished) { //do work mutex.lock(); if(paused) { waitCond.wait(&mutex); paused = false; } mutex.unlock(); } } void abort() { aborted = true; } }; class Controller : public QObject { Q_OBJECT bool aborted=false, paused=false; QWaitCondition waitCond; QMutex mutex; QThread workerThread; public: Controller() : QObject() { Worker *worker = new Worker(aborted, paused, waitCond, mutex); worker->moveToThread(&workerThread); connect(&workerThread, &QThread::finished, worker, &Worker::deleteLater); connect(this, &Controller::startWork, worker, &Worker::doWork); } void abort() { mutex.lock(); paused = true; // Worker starts waiting mutex.unlock(); if(confirmed_by_user) aborted = true; // Don't need to lock because Worker is waiting waitCond.wakeAll(); // Worker resumes loop } signals: void startWork(); }; Controller *cont = new Controller(); emit cont->startWork(); // Start the loop cont->abort(); // Stop the loop 时,都需要调用QMutex::lock

{{1}}