Best way to call a QThread worker object's function on an interval
-
Qt 4.8.
I'm using the QThread worker object model, along the lines of:
QThread *thread = new QThread(...); Worker *worker = new Worker(); worker->moveToThread(thread); connect(thread, SIGNAL(started()), worker, SLOT(initialize())); connect(thread, SIGNAL(finished()), worker, SLOT(cleanup()); connect(thread, SIGNAL(finished()), worker, SLOT(deleteLater()); thread->start();
Where Worker is basically:
class Worker : public QObject { ... public slots: void initialize (); // init various things, on the worker's thread void cleanup (); // clean up various things, on the worker's thread void dostuff (); // must be called at regular intervals, on the worker's thread }
And I want dostuff() to be called, on the worker's thread, every 20 ms (with as much accuracy as reasonably possible).
My question is: What's the best way to set this up?
Qt provides a lot of timer facilities, and also there's the option of which thread the timer itself runs on -- e.g. in the created thread's event loop vs. sending signals from another thread. I'd prefer the former.
My first thought was to subclass QThread, implement timerEvent(), have it emit some signal, the connect that signal to the worker's dostuff() slot, but it's not entirely clear to me which thread timerEvent() runs on (given that the created thread itself lives in the main thread), and I'd rather not introduce an unnecessary Qt::QueuedConnection call. I'd prefer direct.
What's the typical best practice here?
Thanks!
-
I think I figured this out but I would still like some confirmation. One way is to make a QTimer, then move it to the worker thread, then connect appropriately. So something like:
QThread *thread = new QThread(...); Worker *worker = new Worker(); worker->moveToThread(thread); connect(thread, SIGNAL(started()), worker, SLOT(initialize())); connect(thread, SIGNAL(finished()), worker, SLOT(cleanup()); connect(thread, SIGNAL(finished()), worker, SLOT(deleteLater()); QTimer *timer = new QTimer(); timer->setInterval(20); timer->moveToThread(thread); connect(thread, SIGNAL(started()), timer, SLOT(start())); connect(thread, SIGNAL(finished()), timer, SLOT(deleteLater())); connect(timer, SIGNAL(timeout()), worker, SLOT(dostuff())); thread->start();
And then, if I'm not mistaken, the timer will run in the worker thread and Worker::dostuff() will be called directly at an interval on that thread. Right? Is this the simplest approach?
-
Hi,
You could also use startTimer and call your function from timerEvent.
-
@SGaist said in Best way to call a QThread worker object's function on an interval:
Hi,
You could also use startTimer and call your function from timerEvent.
So just to make sure: timerEvent is invoked on the new thread, rather than on its parent thread, then, right?
http://doc.qt.io/qt-4.8/qthread.html#details makes it clear that the QThread's slots are invoked on the parent thread, I am not clear on whether or not that is also true for events.
-
@JCipriani Why not just add the timer to Worker? Like:
class Worker : public QObject { ... public slots: void initialize (); // init various things, on the worker's thread void cleanup (); // clean up various things, on the worker's thread void dostuff (); // must be called at regular intervals, on the worker's thread private: QTimer timer; }
No need to declare it in the main thread and then move it to the worker thread.
-
I am interesting about this topic so I give it a try.
test.h
#ifndef TEST_H #define TEST_H #include <QDebug> #include <QThread> #include <QTimer> class Worker : public QObject { Q_OBJECT QTimer timer; public: Worker() { //do work execute in main thread //connect(&timer, QTimer::timeout, this, &Worker::doWork, Qt::DirectConnection); //timer.start(1000); //do work execute in main thread //QTimer::singleShot(200, this, &Worker::doWork); //do work execute in another thread //startTimer(1000); } protected: void timerEvent(QTimerEvent*) override { qDebug()<<__func__<< " : "<<QThread::currentThread(); } public slots: void doWork() { qDebug()<<__func__<< " : "<<QThread::currentThread(); } }; class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller() { Worker *worker = new Worker; worker->moveToThread(&workerThread); connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &Controller::doWorkSignal, worker, &Worker::doWork); QTimer *timer = new QTimer; timer->setInterval(1000); timer->moveToThread(&workerThread); connect(&workerThread, SIGNAL(started()), timer, SLOT(start())); connect(&workerThread, SIGNAL(finished()), timer, SLOT(deleteLater())); //doWork execute in another thread connect(timer, SIGNAL(timeout()), worker, SLOT(doWork()), Qt::DirectConnection); workerThread.start(); } ~Controller() { workerThread.quit(); workerThread.wait(); } signals: void doWorkSignal(); }; class test { public: test(); }; #endif // TEST_H
main.cpp
#include "mainwindow.h" #include "test.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); qDebug()<< " main thread "<<QThread::currentThread(); Controller cont; MainWindow w; w.show(); return a.exec(); }
In short, the solutions provided by @jsulm or @Sgaist and yours can execute doWork in another thread(specify the connection type by direct connection)
However, solution of @jsulm need Qt::QueuedConnection if you need it to execute in another thread.
But I am not sure about @Sgaist solution rely on Qt::QueuedConnection or not.
May I ask you why you do not want any queued connection?Thanks
-
@tham said in Best way to call a QThread worker object's function on an interval:
However, solution of @jsulm need Qt::QueuedConnection if you need it to execute in another thread.
Nice test, thank you.
Well, @jsulm's solution can be made to work with Qt::DirectConnection by setting the Worker as the QTimer's parent, e.g. in the Worker constructor:
Worker() : timer(this) { ... }
That way the timer is also moved to the new thread along with the Worker. Direct connection or not, this should probably be done anyways, since it also solves the problem of attempting to kill the timer from a different thread (Qt complains, at least 4.8 does) because the Worker's destructor is called on the new thread (presuming you connect QThread::finished to Worker::deleteLater). It also solves a spurious related crash on cleanup (mingw, Windows 7).
May I ask you why you do not want any queued connection?Thanks
It's complicated and also after some testing I'm not sure that I do any more (doWork() might take longer than a timer interval and I didn't want signals to queue up, but thinking about it, that doesn't really matter; also I was concerned about inconsistent latency, but testing seems to show there's no effect and also thinking about it that doesn't matter either; in conclusion I think I was being silly). So... I'll get back to you on that one... 8)
In any case it's still an informative exercise.
For completeness:
class Worker : public QObject { ... public: Worker (...) : timer(this) { ... } public slots: void initialize (); // init various things, on the worker's thread void cleanup (); // clean up various things, on the worker's thread void dostuff (); // must be called at regular intervals, on the worker's thread private: QTimer timer; }
-
@JCipriani Yes, there is no need for queued connection as both Worker and the timer are in the same thread.