Reputation: 754
Currently I have two classes that look something like this:
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
So the idea is that there is a loop running in a Worker
thread, which can be stopped from a Controller
thread.
In the example this is done by calling QCoreApplication::processEvents()
, which allows signals to call slots before returning control to the loop.
It's important the loop is only stopped at the start or end of an iteration.
Although this works nicely, I think QCoreApplication::processEvents()
is pretty expensive, at least when used inside a very long loop (up to thousands in practice).
So my question is, how can I achieve the same result in a better/cheaper way?
Upvotes: 1
Views: 513
Reputation: 754
There are three alternative solutions that I'm aware of at this time.
QThread::requestInterruption
(suggested by @Felix)According to QThread::isInterruptionRequested
:
Take care not to call it too often, to keep the overhead low.
Whereas QCoreApplication::processEvents
makes no remark on performance or memory usage, so I don't think QThread::requestInterruption
is an improvement over QCoreApplication::processEvents
in this case.
std::atomic
(suggested by @Felix)The main characteristic of atomic objects is that access to this contained value from different threads cannot cause data races [...]
The boolean can be stored inside a std::atomic
which can be made a member of the Controller
class instead of the Worker
class. Then we need to pass a reference to aborted
to and store it in Worker
, and set it to true
from Controller
when needed.
I didn't fully test this approach, so please correct me if I got something wrong.
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
QWaitCondition
& QMutex
A boolean paused
will be needed. Controller
and Worker
need read/write access to it.
Set paused
to true
in Controller
when needed.
During the loop in Worker
, if(paused)
: QWaitCondition::wait()
until QWaitCondition::wakeAll()
is called from the calling thread.
QMutex::lock
will need to be called whenever paused
is accessed.
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
Upvotes: 1