从另一个线程打破循环

Breaking a loop from another thread

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

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() 非常昂贵,至少在很长的循环中使用时(实际上多达数千个)。

所以我的问题是,如何以 better/cheaper 方式获得相同的结果?

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

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

根据QThread::isInterruptionRequested

Take care not to call it too often, to keep the overhead low.

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


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

The main characteristic of atomic objects is that access to this contained value from different threads cannot cause data races [...]

布尔值可以存储在 std::atomic 中,它可以成为 Controller class 而不是 Worker class 的成员。然后我们需要将aborted的引用传递给Worker,并在需要的时候从Controller设置为true

我没有完全测试这个方法,所以如果我有什么不对的地方请指正。

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

3。 QWaitCondition & QMutex

需要一个布尔值 pausedControllerWorker 需要 read/write 访问权限。

需要时在Controller中将paused设置为true
Workerif(paused) 的循环中:QWaitCondition::wait() until QWaitCondition::wakeAll() 从调用线程调用。
每当访问 paused 时,都需要调用 QMutex::lock

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