0
votes

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);
2
Put the important of your code in the question, external links serve as a complement because if you break the links your question will not be useful for future readers.eyllanesc
edited. thanks for suggestiondvn0zzz
It would be good if you explain what you are trying to do in the code, it is very confusing to understand without a clear explanation. I recommend you read How to Ask, we are not in your head, empathize with the community.eyllanesc
edited again. thanksdvn0zzz

2 Answers

0
votes

I am not sure what you are trying to do, but a few advices:

  • Most of the time, you don't have to override the run method, except if you need to control the thread activity very closely. Just create a QThread, use moveToThread and start the thread, that's it. You can then use the thread started signals or other signal/slot connection to ensure that it is executed in your thread
  • exec will effectively run the thread event loop, and return only if the thread exists.
  • You can manually run/stop the event loop using exec and quit method
  • While sleeping, or more generally when event loop is not running, No events will be processed or slot executed. So to execute a slot called with Invoke or via a signal from another thread, it is mandatory to call exec after the message is queued in the QThread event loop.

And more generally, always use signals/slots (what you did) to execute code in the proper thread, and rely when you can on higher level classes like QtConcurrent.

For more details, you can have a look at Qt documentation, as well as this (a bit older) blog article

0
votes

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)