[Solved] Threads, timers and moveToThread()
-
Hi!
I was digging up recommended use of QThread with subclassing QObject for worker and then using worker.moveToThread() to run it in thread other than main. And I also want this object do some work in its slot. As I understand it right, we should enter slot, do chunk of work and then return to thread event loop (we can't run infinite loop in slot cause this way we can't return to event loop and correctly interact with thread). So I want to use QTimer inside worker to periodically call its slot.
I wrote simple example and now I'm lost a bit cause it doesn't work well as I supposed.
From docs on QObject moveToThread() I see - "Note that all active timers for the object will be reset. The timers are first stopped in the current thread and restarted (with the same interval) in the targetThread. As a result, constantly moving an object between threads can postpone timer events indefinitely."
I have timer created in constructor and it's definitely created in main thread. Then I move worker to new thread and start it. As docs states timer now should be in new thread. And then I trying to start timer with several methods - connect with Qt::QueuedConnection, with QMetaObject::invokeMethod, but all of them fails with message "timers cannot be started from another thread". Starting with direct call worker->start() works fine but stopping with QTimer::singleShot() fails. So I assume timer don't live main thread with moveToThread() as docs states. When I create timer in worker start() slot it works like it supposed to be.
Could someone clarify how it really works for me please. Thanks in advance.@
#ifndef MY_H
#define MY_H#include <QObject>
#include <QWidget>
#include <QTimer>
#include <QDebug>
#include <QThread>class MyObject;
class MyWidget: public QWidget
{
Q_OBJECT
public:
MyWidget();
private:
MyObject *obj;
QThread *objThread;
};class MyObject: public QObject
{
Q_OBJECT
public:
MyObject(): QObject()
{
timer = new QTimer;
connect(timer, SIGNAL(timeout()), this, SLOT(doWork()));
qDebug() << "Object constructor" << thread();
}
public slots:
void start(int delta = 500)
{
timer->start(delta);
}void stop() { timer->stop(); } void doWork() { qDebug() << "Work in" << thread(); }
private:
QTimer *timer;
};#endif // MY_H@
and
@#include "My.h"
#include <QApplication>
#include <QPushButton>
#include <QHBoxLayout>MyWidget::MyWidget():
QWidget()
{
obj = new MyObject;
objThread = new QThread;
obj->moveToThread(objThread);
objThread->start();
qDebug() << "Now in" << obj->thread();QPushButton *bStart = new QPushButton("Start"); QPushButton *bStop = new QPushButton("Stop"); QHBoxLayout *layout = new QHBoxLayout; layout->addWidget(bStart); layout->addWidget(bStop); setLayout(layout);
// obj->start(); // This start thread without warning
connect(bStart, SIGNAL(clicked()), obj, SLOT(start()), Qt::QueuedConnection); // Fail
connect(bStop, SIGNAL(clicked()), obj, SLOT(stop()), Qt::QueuedConnection); // Fail
// QTimer::singleShot(3000, obj, SLOT(start())); // Fail - QObject::startTimer: timers cannot be started from another thread
// QMetaObject::invokeMethod(obj, "start", Qt::QueuedConnection); // Fail - the same
}int main(int argc, char *argv[])
{
QApplication app(argc, argv);
qDebug() << "Main thread" << app.thread();
MyWidget w;
w.resize(400, 400);
w.show();return app.exec();
}
@ -
The timer was not moved to the thread, it has to be moved explicitly or be made a child of the moved object, because when the object is moved, all its children are moved with it.
So you should simply create the timer like this:
@timer = new QTimer(this);@Then you can remove the explicit connection type from the connect call.
-
alexisdm, thanks a lot! I missed this important step.
-
[quote author="alexisdm" date="1353845378"]The timer was not moved to the thread, it has to be moved explicitly or be made a child of the moved object, because when the object is moved, all its children are moved with it.
So you should simply create the timer like this:
@timer = new QTimer(this);@Then you can remove the explicit connection type from the connect call.[/quote]
I was looking at an article which a "tutorial":http://qt-project.org/forums/viewthread/14806 was created on this forum and an important point was "NEVER allocate heap objects (using new) in the constructor of the QObject class as this allocation is then performed on the main thread and not on the new QThread instance, meaning that the newly created object is then owned by the main thread and not the QThread instance"__
Can you elaborate what you mean by removing the explicit connection type from the connect call?
-
[quote author="astodolski" date="1355750978"]
I was looking at an article which a "tutorial":http://qt-project.org/forums/viewthread/14806 was created on this forum and an important point was "NEVER allocate heap objects (using new) in the constructor of the QObject class as this allocation is then performed on the main thread and not on the new QThread instance, meaning that the newly created object is then owned by the main thread and not the QThread instance"__
[/quote]"never" is a bit strong, and other tutorials don't give that limitation. Children objects are moved automatically to the new thread wherever they are allocated from, so if there is a crash as the tutorial author says there is a problem somewhere else.[quote]Can you elaborate what you mean by removing the explicit connection type from the connect call?[/quote]I mean to remove the Qt::QueuedConnection parameter from the QObject::connect call: if you don't specify that parameter, it defaults to "Qt::AutomaticConnection":http://qt-project.org/doc/qt-4.8/qt.html#ConnectionType-enum which will automatically be queued if the signal and the slot belongs to different threads.
So most of the time, you can omit it. I think that Qt::QueuedConnection is mostly useful within a single thread, when you don't want the slot execution to be "inlined" into the emitting function.