QThread and QTimer



  • Hi,

    I have a slot that is connected to QTimer's signal. I like to move this function to another thread since it's a very time consuming process but I also need to start or stop the function from running in runtime so I have to call timer->start() or timer->stop(). so far I've done this:

    @m_frameTimer = new QTimer(0);
    m_frameTimer->setInterval(0);
    connect(m_frameTimer, SIGNAL(timeout()), this, SLOT(timerFired()));

    m_thread = new QThread(this);
    m_frameTimer->moveToThread(m_thread);@

    and I call the m_thread->start() when a specific button is clicked and then I call timer->start() if the process is ready otherwise timer->stop. but it doesn't work and it gives me the following error:
    "QObject::startTimer: QTimer can only be used with threads started with QThread".



  • When you connect objects that live in the same thread, you get a direct connection by default, when you connect objects that live in different threads, you get a queued connection by default. In your case you implicitly create a direct connection and then move one of the objects to another thread.



  • also the timer (which you move to another thread) doesn't really do the heavy work. I guess that job is done in the timerFired slot which is still executed in the context of the main thread, because the object that is implementing this slot was not moved to another thread.



  • BTW the actual error message is due to the fact QTimer needs a thread with a running event loop to work. And the event loop of QThread does not start by default unless you explicitly start it.

    As KA51O said, the timer does not need to be in a separate thread, you are not offloading anything this way, but if you still want to do it, you have to have the thread your QTimer is into running its event loop.

    @class MyThread : public QThread
    {
    Q_OBJECT
    public:
    explicit MyThread(QObject *parent = 0) : QThread(parent) {
    t = new QTimer();
    t->moveToThread(this);
    t->setInterval(1000);
    connect(t, SIGNAL(timeout()), this, SLOT(timeout()));
    }

    ~MyThread() {
        if (t) t->deleteLater();
    }
    

    protected:
    void run() {
    t->start();
    exec();
    }

    signals:
    void timerElapsed();

    private slots:
    void timeout() {
    emit timerElapsed();
    }

    private:
    QTimer *t;
    };@

    Note that the QTimer is created without a parent because otherwise it cannot be moved, so remember to delete it manually.



  • [quote author="utcenter" date="1364392954"]When you connect objects that live in the same thread, you get a direct connection by default, when you connect objects that live in different threads, you get a queued connection by default. In your case you implicitly create a direct connection and then move one of the objects to another thread.[/quote]

    No, you create an auto connection by default. And an auto connection is evaluated at runtime, every time the signal is send.

    [quote author="utcenter" date="1364396202"]BTW the actual error message is due to the fact QTimer needs a thread with a running event loop to work. And the event loop of QThread does not start by default unless you explicitly start it.[/quote]
    Also not true. A vanilla QThread that is started has an eventloop by default. See the implementation of QThread::run():
    @
    /*!
    The starting point for the thread. After calling start(), the
    newly created thread calls this function. The default
    implementation simply calls exec().

    You can reimplement this function to facilitate advanced thread
    management. Returning from this method will end the execution of
    the thread.
    
    \sa start() wait()
    

    */
    void QThread::run()
    {
    (void) exec();
    }
    @



  • [quote author="utcenter" date="1364396202"]BTW the actual error message is due to the fact QTimer needs a thread with a running event loop to work. And the event loop of QThread does not start by default unless you explicitly start it.

    As KA51O said, the timer does not need to be in a separate thread, you are not offloading anything this way, but if you still want to do it, you have to have the thread your QTimer is into running its event loop.
    [/quote]

    Acctually a standard QThread started by calling its start() function does have its own eventloop, because the default implemetation of QThread::run() calls exec(). Thats why you can simply do this:
    @
    QThread* thread = new QThread(this);
    MyWorker* worker = new MyWorker();
    worker->moveToThread(thread);

    connect(thread, SIGNAL(started()), worker, SLOT(doWork()));
    //further connect statements for ending and deleting everything properly

    thread->start();
    @

    BTW I would not recommend subclassing QThread unless you really have to.



  • @Andre - if a QThread does run an event loop by default, why does QTimer refuse to start once moved to it?



  • You mean this does not work ?
    @
    QThread* thread = new QThread(this);
    QTimer* timer = new QTimer();
    timer->setInterval(1000);
    timer->setSingleShot(true);
    timer->moveToThread(thread);

    connect(thread, SIGNAL(started()), timer, SLOT(start()));
    connect(timer, SIGNAL(timeout()), thread, SLOT(quit()));
    connect(thread, SIGNAL(finished()), timer, SLOT(deleteLater()));
    connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

    thread->start();
    @

    I'm not sure if you can call timer->start() from another thread but you should be able to do this:
    @
    connect(objectInMainThread, SIGNAL(startTimer()), timer, SLOT(start()));
    emit startTimer();
    @



  • I mean it will not work if you move the timer to the thread.



  • Sorry forgot to add the moveToThread() call in my example above. Corrected it.



  • [quote author="utcenter" date="1364397701"]@Andre - if a QThread does run an event loop by default, why does QTimer refuse to start once moved to it?[/quote]

    It doesn't. I just tried the code posted by KA51O above. It works without complaint.



  • But only because it is called from the same thread. In the code above it is still impossible to start the timer instead of using the connection from the thread.



  • Sure, the timer must be started from the thread. But if you don't want to use a connection to the thread's started signal (or any other suitable signal: your choice), you can also do something like this:
    @
    QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection);
    @

    That also works. Note that for many objects belonging to a thread B, it would be unsafe to call methods on them directly from thread A. It's just QTimer that's nice enough to tell you that you're doing something dangerous. I don't see why that is a problem.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.