从另一个线程打破循环
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
需要一个布尔值 paused
。 Controller
和 Worker
需要 read/write 访问权限。
需要时在Controller
中将paused
设置为true
。
在 Worker
、if(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
目前我有两个 类 看起来像这样:
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
需要一个布尔值 paused
。 Controller
和 Worker
需要 read/write 访问权限。
需要时在Controller
中将paused
设置为true
。
在 Worker
、if(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