4

When I run the following code using Qt 5.3.2 the remaining time is set to -1371648957.

QTimer *timer = new QTimer(this);
timer->setSingleShot(true);
timer->start(100);
qDebug() << "remaining" << timer->remainingTime();

If I continue to print the remaining time in a loop afterwards, the negative value just increases and therefore timeout() is never fired. This really makes no sense to me.

To give a bit of context, this code runs inside a separate thread and the QTimer is not created in the constructor of the threaded object.

Here is some updated code to make things clearer.

void MainObject::SomeMethod(){
    // main thread
    ObjectWithTimer *owt = new ObjectWithTimer();    
    QThread *someThread = new QThread();
    owt->moveToThread(someThread);
    connect(someThread, SIGNAL(started()), owt, SLOT(doStuff()));
    someThread->start();
}

void ObjectWithTimer::doStuff(){
    while(condition){
        // do various stuff
        // among other things emit SIGNALS to the main thread, that are received
        // so the event loop in the thread is running
        QTimer *timer = new QTimer(this);
        timer->setSingleShot(true);
        timer->start(100);
        qDebug() << "remaining" << timer->remainingTime();

        connect(timer, SIGNAL(timeout()), this, SLOT(onClientTimeoutTest()));
    }
}
void ObjectWithTimer::onClientTimeoutTest(){
    // this method is of course never fired, since the remaining time never reaches 0
}

I already checked that the timer creation runs correctly in the separate thread and that Qts event loop inside the thread is working, since I can emit signals that the main thread receives.

Also it makes not difference if I set the timer like this

timer->setSingleShot(true);
timer->setInterval(100);
timer->start();

If I change the number of seconds to 100000 or 0, the remaining time only slightly changes for example to -1374002988 which it does anyway when I restart the application, but the length remains the same.

Also I checked with the debugger on the timer->remainingTime() line and the internal inter variable is correctly set to 100.

Could this be a memory adress or something like that?

Dreiven
  • 687
  • 3
  • 9
  • 22
  • 1
    You state that the QTimer is created and run in the separate thread. Where exactly? Is it created in the constructor of someObject, before it's moved to the new thread? I suspect there's an issue of thread affinity here. If you show all the code, we may get a better idea of the problem. – TheDarkKnight Feb 17 '15 at 14:00
  • "timeout() is never fired" contradicts the later statement "I already checked that the timer creation runs correctly in the separate thread and that Qts event loop inside the thread is working" – TheDarkKnight Feb 17 '15 at 14:02
  • Have you tried set a slot function to call when singleshot is finished? http://doc.qt.io/qt-5/qtimer.html#singleShot – dfranca Feb 17 '15 at 14:03
  • See the updated code, hope that makes it clearer. @Merlin069 I checked that the event loop itself is running by checking emit signals, not specifically the `timeout()` signal, which is not fired. – Dreiven Feb 17 '15 at 14:14
  • What happens if you set a bigger value ? – Thomas Ayoub Feb 17 '15 at 14:15
  • Changing the the number of seconds doesn't change the length of the number. – Dreiven Feb 17 '15 at 14:19
  • Can you create a minimal example, that demonstrates the problem? QTimer works fine for me. – BЈовић Feb 17 '15 at 14:23
  • If you call QApplication::processEvents before calling timer->remainingTime, does that make a difference? – TheDarkKnight Feb 17 '15 at 14:26
  • 1
    I tested it, and i get the same result as OP. `QApplication::processEvents` does make it return the expected result. Maybe it doesn't change the value before it goes back to the event loop? – thuga Feb 17 '15 at 14:29

1 Answers1

2
QTimer *timer = new QTimer(this);
timer->setSingleShot(true);
timer->start(100);
qDebug() << "remaining" << timer->remainingTime();

As QTimer works on an event loop, I suspect that calling remainingTime may return invalid at this point, as the Timer has not yet been fully initialised.

If you follow the source code for QTimer, you'll see that it actually uses the timer of QObject and calls QObject::startTimer. The source for QObject (line 1632) shows that an event is dispatched at this point: -

return d->threadData->eventDispatcher->registerTimer(interval, this);

Therefore, allow the code to return to the main event loop after calling start and before requesting the remaining time.

TheDarkKnight
  • 27,181
  • 6
  • 55
  • 85
  • 1
    It works fine when calling from the main thread without going to the event loop (if the event loop was started before checking the remaining time). It seems that when thread emits the `started` signal, it's connected via a `Qt::DirectConnection` and in this case the `QTimer::remainingTime` returns some random value. Using `Qt::QueuedConnection` makes it work fine. – thuga Feb 17 '15 at 14:36
  • 1
    I would expect Qt::DirectConnection to be selected automatically, as the QTimer and ObjectWithTimer are running on the same thread (thread affinity). Using Qt::QueuedConnection has the same effect as I suggested in that it will return to the event loop before being called. – TheDarkKnight Feb 17 '15 at 14:46
  • 1
    I meant connecting the `QThread::started` signal to the `ObjectWithTimer::doStuff()` slot. – thuga Feb 17 '15 at 14:46
  • Thanks! I have to create the QTimer in a loop, so that seems to block the thread event loop. Using `connect(someThread, SIGNAL(started()), owt, SLOT(doStuff()), Qt::QueuedConnection);` results in the timer having a proper remaining time and counting down correctly, but `timeout()` is still not fired, eventhough I also tried a `Qt::QueuedConnection` on that `connect()` as well. – Dreiven Feb 17 '15 at 14:51
  • 1
    @Dreiven If you have some loop running, the slots will not be executed. Your thread needs to go to its event loop to process the slots. Remove the loop and use a `QTimer` instead, – thuga Feb 17 '15 at 14:53
  • 1
    In which case, it looks like the event loop for the new thread is not yet ready to receive events. – TheDarkKnight Feb 17 '15 at 14:55
  • It works if I add `QApplication::processEvents();` in the loop, so you're right of course, I should use a QTimer for that. Thanks for your help! Still I wonder why I can `emit` signals eventhough I accidentally block the event loop. – Dreiven Feb 17 '15 at 14:56
  • 1
    @Dreiven, blocking the event loop is almost never a good idea and probably the reason why your slot isn't being called. I only suggested QApplication::processEvents to highlight that it was an event loop problem. – TheDarkKnight Feb 17 '15 at 14:57
  • 1
    @Dreiven Use `QApplication::processEvents` for testing purposes only. Avoid using it in real code. – thuga Feb 17 '15 at 14:57
  • @Dreiven, refactor your code to remove the while(condition) loop. Remove checking the condition and have a signal triggered, which is the correct method for an event-driven system such as Qt. – TheDarkKnight Feb 17 '15 at 14:59
  • Still, according to the documentation `remainingTime()` should return either 0, -1 or the remaining time in milliseconds which should probably be a positive integer, since you get an `invalid argument` error if you try to use `setInterval(-1)`. So I think this is a bug in Qt and I filed a bug report for it: https://bugreports.qt.io/browse/QTBUG-44534 – Dreiven Feb 18 '15 at 12:22