qtqtimerqeventloop

QEventLoop in QTimer slot


I have multiple QTimer in the same thread, in the slot connected to the QTimer, I use QEventLoop for synchronization, like http requests, but I found different QTimers may affect each other when they are started in different orders.

Here is my simple test code snippet:

Case 1

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTimer t1;
    QTimer t2;
    QObject::connect(&t1, &QTimer::timeout, []()->void{
                         qDebug() << QDateTime::currentDateTimeUtc() << "T1...";
                         QEventLoop loop;
                         // t1 slot run time 3000ms
                         QTimer::singleShot(3000, &loop, SLOT(quit()));
                         loop.exec();
                     });
    QObject::connect(&t2, &QTimer::timeout, []()->void{
                         qDebug() << QDateTime::currentDateTimeUtc() << "T2...";
                         QEventLoop loop;
                         // t2 slot run time 100ms
                         QTimer::singleShot(100, &loop, SLOT(quit()));
                         loop.exec();
                     });
    // interval 1000ms, start t1 first and then t2
    t1.start(1000);
    t2.start(1000);
    return a.exec();
}

Output:

QDateTime(2022-03-21 14:00:51.014 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:00:51.016 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:00:54.025 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:00:54.027 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:00:58.018 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:00:58.019 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:01:01.014 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:01:01.015 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:01:04.016 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:01:04.016 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:01:07.019 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:01:07.020 UTC Qt::TimeSpec(UTC)) T1...

From the output, you can see t2 is affected by t1, the actual interval is about 3000~4000ms, both for t1 and t2, I think t2 should not be affected

Case 2

Just change the QTimer start order, but things will become different

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTimer t1;
    QTimer t2;
    QObject::connect(&t1, &QTimer::timeout, []()->void{
                         qDebug() << QDateTime::currentDateTimeUtc() << "T1...";
                         QEventLoop loop;
                         // t1 slot run time 3000ms
                         QTimer::singleShot(3000, &loop, SLOT(quit()));
                         loop.exec();
                     });
    QObject::connect(&t2, &QTimer::timeout, []()->void{
                         qDebug() << QDateTime::currentDateTimeUtc() << "T2...";
                         QEventLoop loop;
                         // t2 slot run time 100ms
                         QTimer::singleShot(100, &loop, SLOT(quit()));
                         loop.exec();
                     });
    // interval 1000ms, start t2 first and then t1
    t2.start(1000);
    t1.start(1000);
    return a.exec();
}

Output:

QDateTime(2022-03-21 14:04:53.653 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:04:53.656 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:54.659 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:55.655 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:56.656 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:04:56.656 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:57.664 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:58.657 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:04:59.665 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:04:59.667 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:05:00.660 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:05:01.662 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:05:02.652 UTC Qt::TimeSpec(UTC)) T2...
QDateTime(2022-03-21 14:05:03.650 UTC Qt::TimeSpec(UTC)) T1...
QDateTime(2022-03-21 14:05:03.653 UTC Qt::TimeSpec(UTC)) T2...

Now t1 does not affect t2 any more, actual interval for t1 is about 3000~4000ms, t2 is 1000ms.

I am really confused why different start order will have different results. Could anyone help me to explain this?

Platform: Qt 5.5.0 MinGW 32bit, Windows

IDE: Qt Creator 3.4.2

You can simply run my test code directly in main() and check the test results.

Thanks.

Edit 1:

I am using QEventLoop for sync http request of RESTFul API, like this:

// m_http is QNetworkAccessManager
QNetworkReply *reply = m_http->get(request);
QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();

In timer slot, there is only a sync http request, to poll data periodly. Normally the request will be finished very fast, but sometimes it will cost more than 1000ms if error occurs. My application need to communicate with multiple server at the same time, and each connection have a timer to do the polling, all connection instances are in the same thread, so all timers share the same eventloop. My tests is a simple way to simulate the request, using QTimer::singleShot as the time cost by request. Is there any better way to do sync http request in QT? Using QEventLoop may be not the best.


Solution

  • First, check your QTimer::timerType(): the default is Qt::CoarseTimer, which has a 5% precision. You may need Qt::PreciseTimer instead.

    Second, the main Qt event loop will serialize ALL events. Whatever they are. It's an unique pipeline (who can easily become your application's bottleneck, BTW). It also have the bad habit to "pack" some events together (like mouse events) before processing them.

    Finally, a QTimer::singleShot is also a real timer, and you launch one inside a slot - which is, basically, executed within event loop context, where you call manually an event loop processing, on two distinct QEventLoop objects...

    Honestly, it's not very surprising that your timers do strange things. Could you precise exactly what you need to do with these timers? Because the only thing that your QEventLoop trick do is to block/mess the timer during the requested period, i.e. 3000 ms for T1 and 100 ms for T2, which will obviously mess with the basic 1000 ms period you requested for each...

    Normally, you connect the reception signal (finished(QNetworkReply*) for QNetworkAccessManager, but it would be readyRead() for QIODevice) to your slots, and you use sender() to allow a single slot to process various objects simultaneously. You don't synchronize manually anything, you let Qt handles that for you - that's one of the benefits of using a framework. So, the "reply reception" is done this way.

    For sending requests, it's up to you. You can use various solutions:

    1. Sending them ASAP once requested (i.e. through a button click).
    2. Buffering them into a FIFO container, using a QTimer to send them without hammering both your machine and the servers. Requests are pushed into the buffer ASAP.
    3. Threading this part - beware, with Qt, you'll need most of the time to call moveToThread() to get this to work properly.

    I would prefer the solution #2, with a queue and a specific timer for each targetted server - this will allow a very basic QoS and will avoid that a huge number of requests for one server forbids other servers to be used. You can use, before that, a dispatcher function that would automatically choose the correct queue to use through a map associating a server name (+protocol if needed) and its specific queue and timer.

    And when you don't do anything, neither requests (they are all sent) and you're waiting for replies, you let the main Qt event loop do its job: avoiding blocking the message pump - it would produce the "The application is not responding" message. Also, it allows to reduce the CPU load, to get all mouse events for your application, and to refresh GUI elements like a progress bar.