Search code examples
qtqthreadqobject

QT Eventloop and slot handle?


I'm created a demo to study about QObject, QThread and QT Signal/Slot as links below

The idea is:

I created an ExtentQThread which extent from QThread and implement run() function which will loop for loopTimeoutMsec (set on constructor) before call exec() (which make it enter its thread event loop). I created one ExtentQThread extQThread1 instance from main with loopTimeoutMsec set to 10000.

Then I created two instances of ExtentQObject from main thread. extQObject10 is created and moved to extQThread1 and extQObject11 which is not moved.

Test expectation:

  • extQObject11 slot run on Main thread at around (timeout = 5000): PASSED
  • extQObject10 slot run on extQThread1: PASSED
  • extQObject10 slot run on extQThread1 at around (loopTimeoutMsec = 10000): NOT PASSED

[main.cpp]

#include <QCoreApplication>

#include <QTimer>
#include "extentqthread.h"

long baseMSecsSinceEpoch;

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    baseMSecsSinceEpoch = QDateTime::currentMSecsSinceEpoch();

    qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << "Main thread 1" << QThread::currentThreadId();

    // === test1
    ExtentQThread extQThread1("extQThread1 created from main thread", 10000);

    ExtentQObject extQObject10("extQObject10 created from main thread then moved to extQThread1");
    extQObject10.moveToThread(&extQThread1);

    ExtentQObject extQObject11("extQObject11 created from main thread");

    extQThread1.start();

    // 1.0 to test signal of extQObject10 which is moved to extQThread1
    // and signal of extQObject11 which is not moved
    long timeout = 5000;
    QTimer::singleShot(timeout, [&extQThread1, &timeout]() {
        qDebug() << "\n==" << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << "timeout" << timeout
                 << "\n>> To test signal of extQObject10 which is moved to extQThread1 and signal of extQObject11 which is not moved"
                 << "\n>> extQThread1.isRunning()" << extQThread1.isRunning();
    });
    QTimer::singleShot(timeout, &extQObject10, &ExtentQObject::onExtentQObjectFirstSlot);
    QTimer::singleShot(timeout, &extQObject11, &ExtentQObject::onExtentQObjectFirstSlot);

    qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << "Main thread 2" << QThread::currentThreadId();

    return a.exec();
}

[extentqthread.cpp]

#include "extentqthread.h"

extern long baseMSecsSinceEpoch;

ExtentQThread::ExtentQThread(QString name_, long loopTimeoutMsec_)
{
    name = name_;
    loopTimeoutMsec = loopTimeoutMsec_;

    qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
             << "instance" << name
             << "loopTimeoutMsec" << loopTimeoutMsec
             << "created on thread" << QThread::currentThreadId();
}

void ExtentQThread::run() {
    qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
             << "instance" << name
             << "STARTED on thread" << QThread::currentThreadId();

    ExtentQObject extQObject("extQObject created from (" + name + ")");
    connect(this, &ExtentQThread::runFirstSlot, &extQObject, &ExtentQObject::onExtentQObjectFirstSlot);

    if (loopTimeoutMsec < 0) {
        while(1) {};
    } else {
        QThread::msleep(loopTimeoutMsec);
    }

    qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
             << "instance" << name
             << "before exec() on thread" << QThread::currentThreadId();

    exec();

    qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
             << "instance" << name
             << "after exec() on thread" << QThread::currentThreadId();

    if (loopTimeoutMsec < 0) {
        while(1) {};
    } else {
        QThread::msleep(loopTimeoutMsec);
    }

    qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
             << "instance" << name
             << "STOPPED on thread" << QThread::currentThreadId();
}

void ExtentQThread::onExtentQThreadFirstSlot() {
    qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
             << "instance" << name
             << "run on thread" << QThread::currentThreadId();

    Q_EMIT runFirstSlot();
}

[extentqobject.cpp]

#include "extentqobject.h"

extern long baseMSecsSinceEpoch;

ExtentQObject::ExtentQObject(QString name_)
{
    name = name_;
    qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
             << "instance" << name
             << "created on thread" << QThread::currentThreadId();
}

void ExtentQObject::onExtentQObjectFirstSlot() {
    qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
             << "instance" << name
             << "run on thread" << QThread::currentThreadId();
}

Here is the output

0 Main thread 1 0x7fdc8f3f3740
1 ExtentQThread::ExtentQThread(QString, long int) instance "extQThread1 created from main thread" loopTimeoutMsec 10000 created on thread 0x7fdc8f3f3740
1 ExtentQObject::ExtentQObject(QString) instance "extQObject10 created from main thread then moved to extQThread1" created on thread 0x7fdc8f3f3740
1 ExtentQObject::ExtentQObject(QString) instance "extQObject11 created from main thread" created on thread 0x7fdc8f3f3740
1 Main thread 2 0x7fdc8f3f3740
1 virtual void ExtentQThread::run() instance "extQThread1 created from main thread" STARTED on thread 0x7fdc8aa03700
1 ExtentQObject::ExtentQObject(QString) instance "extQObject created from (extQThread1 created from main thread)" created on thread 0x7fdc8aa03700

== 4754 timeout 5000 
>> To test signal of extQObject10 which is moved to extQThread1 and signal of extQObject11 which is not moved 
>> extQThread1.isRunning() true
4754 void ExtentQObject::onExtentQObjectFirstSlot() instance "extQObject11 created from main thread" run on thread 0x7fdc8f3f3740
10001 virtual void ExtentQThread::run() instance "extQThread1 created from main thread" before exec() on thread 0x7fdc8aa03700
14756 void ExtentQObject::onExtentQObjectFirstSlot() instance "extQObject10 created from main thread then moved to extQThread1" run on thread 0x7fdc8aa03700

As my understanding I would expect: ExtentQObject::onExtentQObjectFirstSlot() instance "extQObject10 created from main thread then moved to extQThread1" run on thread 0x7fdc8aa03700 run at around 10000(msec) instead of 14756(msec). because the signal is emited at 5000(msec) and the exec() which enter ExtentQthread is run after 10000(msec) and it should handle onExtentQObjectFirstSlot then.

Could anyone give some explanation?

////

  • I tried to change from QTimer::singleShot to QTimer instance it gave expected behaviour (the diff as below)

diff --git a/main.cpp b/main.cpp index ed45d23..0ebabf3 100644 --- a/main.cpp +++ b/main.cpp @@ -25,14 +25,17 @@ int main(int argc, char *argv[])

     // 1.0 to test signal of extQObject10 which is moved to extQThread1
     // and signal of extQObject11 which is not moved
+    QTimer timer;
+    timer.setSingleShot(true);
     long timeout = 5000;
-    QTimer::singleShot(timeout, [&extQThread1, &timeout]() {
+    QObject::connect(&timer, &QTimer::timeout, [&extQThread1, &timeout]() {
         qDebug() << "\n==" << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << "timeout" << timeout
                  << "\n>> To test signal of extQObject10 which is moved to extQThread1 and signal of extQObject11 which is not moved"
                  << "\n>> extQThread1.isRunning()" << extQThread1.isRunning();
     });
-    QTimer::singleShot(timeout, &extQObject10, &ExtentQObject::onExtentQObjectFirstSlot);
-    QTimer::singleShot(timeout, &extQObject11, &ExtentQObject::onExtentQObjectFirstSlot);
+    QObject::connect(&timer, &QTimer::timeout, &extQObject10, &ExtentQObject::onExtentQObjectFirstSlot);
+    QObject::connect(&timer, &QTimer::timeout, &extQObject11, &ExtentQObject::onExtentQObjectFirstSlot);
+    timer.start(timeout);

Solution

  • I found the reason, may be this is helpful for someone

    QTimer::singleShot(timeout, &extQObject10, &ExtentQObject::onExtentQObjectFirstSlot);
    

    In this case QTimer::singleShot call to this overloaded function

    QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj)
        : QObject(QAbstractEventDispatcher::instance()), hasValidReceiver(r), receiver(r), slotObj(slotObj)
    {
    
        timerId = startTimer(msec, timerType);
        if (r && thread() != r->thread()) {
            // Avoid leaking the QSingleShotTimer instance in case the application exits before the timer fires
            connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater);
            setParent(0);
            moveToThread(r->thread());
        }
    }
    

    this created a timer instance (let call timerA) and because extQObject10, which is receiver set for QTimer::singleShot, is moved to extThread1 so timerA is moved there too.

    Because QTimer extended QObject so it inherited bool QObject::event(QEvent *e) from QObject which handle thread change as below

        case QEvent::ThreadChange: {
        Q_D(QObject);
        QThreadData *threadData = d->threadData;
        QAbstractEventDispatcher *eventDispatcher = threadData->eventDispatcher.load();
        if (eventDispatcher) {
            QList<QAbstractEventDispatcher::TimerInfo> timers = eventDispatcher->registeredTimers(this);
            if (!timers.isEmpty()) {
                // do not to release our timer ids back to the pool (since the timer ids are moving to a new thread).
                eventDispatcher->unregisterTimers(this);
                QMetaObject::invokeMethod(this, "_q_reregisterTimers", Qt::QueuedConnection,
                                          Q_ARG(void*, (new QList<QAbstractEventDispatcher::TimerInfo>(timers))));
            }
        }
        break;
    

    Here, QMetaObject::invokeMethod results a slot handle by extQThread1, so it is will be handled after loopTimeoutMsec which is set to extQThread1. After that, the timerA start running and will fire after the timeout set for it, at that time onExtentQObjectFirstSlot will be called on extObject10.

    In summary, onExtentQObjectFirstSlot will be called on extObject10 after loopTimeoutMsec (set for extQThread1) + timeout (set for QTimer::singleShot)