QTimer is not triggered immediately after completing Blocking task

105 Views Asked by At

Background: In our Embedded QT application(based on ARM processor), there is a Qtimer which will timeout for every 20ms. Updating the user interface objects is done inside the timeout handler of this timer.

Also, we have a computationally heavy task which I replaced with a "for" loop with some 300000 iterations and print statements inside it for easy understanding. I'm showing BusyIndicator during this operation.

Issue1: When the BusyIndicator is being shown, the Qtimer which is set for 20ms is getting timeout for every 40+ seconds.(Anyway this is not causing any issue at this point of time because, no UI related operations are done when BusyIndicator is displayed).

Issue2(Actual Issue): Once the heavy task is completed, the BusyIndicator is disabled. The Qtimer that is fired after disabling BusyIndicator, also takes 40+ sec to timeout for the first time, then it is recovering and firing for every 20ms subsequently. Because of this, as soon as the BusyIndicator is disabled and any valid page is displayed, the UI is not responding for those 40+ seconds.

Question:

  1. Why BusyIndicator is affecting the Qtimer's timeout value?
  2. Is there any way where the BusyIndicator does not affect the Qtimer timeout value?
  3. Any other solution for the issue that I'm facing?

Note: I created a dummy project in QtCreator and created a demo Desktop based application(in both Windows and Ubuntu) with the same scenario(i.e QTimer being interrupted for some time by a blocking heavy task), I could not see any issue(QTimer fires immediately after the blocking task is completed). Please find below the code of demo application that simulated the scenario of my issue:

main.cpp

#include "main.h"

myClass *classPtr;
static int loopStart=0;

int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);
    myClass classs;
    classPtr = &classs;

    return app.exec();
}

myClass::myClass(QObject *parent):
    QObject(parent)
{

    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &myClass::timerEvnt);
    timer->start(20);
}

void myClass::timerEvnt()
{
    myClass::looop();
    if(loopStart >= 5)
        qDebug()<<"timer continuation";
    loopStart++;
}

void myClass::looop()
{
    int j=0;
    if(loopStart == 5)
    {
        for(int i=0; i<300000;i++)
        {
            qDebug()<<"iterations="<<i;
        }
        qDebug()<<"#####################for loop comp="<<j;
    }
}

main.h

#ifndef MAIN_H
#define MAIN_H
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QObject>
#include <QTimer>
#include <QQuickView>
#include <QElapsedTimer>

class myClass : public QObject
{
    Q_OBJECT
public:
    QTimer *timer;
    explicit myClass(QObject* parent = nullptr);
    void timerEvnt();
    void looop();
};

#endif // MAIN_H

main.qml

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    Rectangle {
        width: parent.width
        height: parent.height

        Text {
            id: displayText
            anchors.centerIn: parent
            text: "Initial Message"
            font.pixelSize: 20
        }
    }
}

Thanks in Advance

1

There are 1 best solutions below

4
On

The best approach to responsiveness should not block or delay your UI any more than it needs to.

In your code, you are executing 300 qDebug() statements every 20ms. In other words, you are printing 150000 lines a second. In addition, this all happens on the same thread that the QML engine is on. So, hence, other things the QML engine which includes the BusyIndicator animation is being affected.

You need to structure your code to output at a rate that we are happy to process the results and is much more friendlier to the QML engine thread, e.g. 200ms interval or 5 times a second.

Consider the following code.

Here, regardless of the platform (could be intel PC, could be an embedded ARM platform), we guarantee that the Timer code will only utilize 100ms of a Timer on a 200ms interval. i.e. we capped it to use no more than 50% of the QML engine thread, giving the code ample time to catch up.

We also update the UI/UX at most once every 200ms or 5 times a second.

The number of iterations on a Timer will vary, and that's okay. On fast intel PC platforms it can run more iterations on slower ARM-embedded devices it will run less. But, we guarantee that the code is cross-platform friendly and scalable.

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
    property double calcPI: 4
    property int div: 1
    property int totalIter: 0
    ColumnLayout {
        anchors.centerIn: parent
        spacing: 10
        Text {
            Layout.alignment: Qt.AlignHCenter
            text: "%1 (%2 total iterations)".arg(calcPI.toString()).arg(totalIter)
        }
        BusyIndicator {
            Layout.alignment: Qt.AlignHCenter
        }
    }
    Timer {
        interval: 200
        repeat: true
        running: true
        onTriggered: {
            let start = Date.now();
            while (Date.now() - start < 100) {
                calcStep();
            }
        }
    }
    function calcStep() {
        div += 2;
        calcPI -= 4 / div;
        div += 2;
        calcPI += 4 / div;
        totalIter++;
    }
}

You can Try this code online!

In C++, the above pattern would be translated as:

myClass::myClass(QObject *parent)
    : QObject(parent)
    , result(4.0)
    , div(1)
{

    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &myClass::timerEvnt);
    timer->start(200); // set QTimer to run at 200ms intervals
}

void myClass::timerEvnt()
{
    // run as many iterations as we can but cap it to 100ms.
    qint64 start = QDateTime::currentMSecsSinceEpoch();
    while (QDateTime::currentMSecsSinceEpoch() < 100)
    {
        calcStep();
    }

    // update the UI/UX only once every 200ms.
    emit resultChanged();
}

void myClass::calcStep()
{
    // implementation of calcStep() must be short and quick.
    // Idealistically, no loops here.
    div += 2;
    result -= 4.0 / div;
    div += 2;
    result += 4.0 / div;
}