QSignalSpy can not be used with threads
Asked Answered
S

2

6

I wrote a thread that executes a worker object. Everything works fine. Also the resulting signals are emitted as they should. Of course I took care of the usual mistakes regarding thread/object affinity.

Today I wrote an automated module test for those workers/threads. I created a QSignalSpy to wait for a signal that is emitted by the worker object (which was moved to the thread) like this:

QSignalSpy spy(worker, SIGNAL(Success()));
thread.ExecuteWorker();
QVERIFY(spy.wait()); // Error in this line

I am getting a well known error in the marked line:

QObject::killTimer: timers cannot be stopped from another thread

First I exepected an error on my side because some code in wait() was executed in the wrong thread. Then I found the following code in the implementation of QSignalSpy:

if (!QMetaObject::connect(obj, sigIndex, this, memberOffset, Qt::DirectConnection, 0))
{
   qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect.");
   return;
}

This obviously means QSignalSpy uses DirectConnection all time and can not be used to monitor signals of objects living in different threads.

Why did they program it that way in Qt5.3? Is that a mistake or is it intended behaviour? How can I work around this limitation?

Stome answered 6/12, 2014 at 20:54 Comment(0)
Q
6

This is unfortunately a long-standing issue, more than six years to be fair:

QSignalSpy crashes if signal is emitted from worker thread

I met Jason at the Qt Contributor Summit a couple of years ago, but then he left Nokia not much after that as Nokia closed the Brisbane office where he was working. After that, there was not much contribution going on in this test module of Qt, sadly.

There was recently more discussion about it on the mailing list, too:

Why is QSignalSpy using Qt::DirectConnection?

The solution proposed by Roland was this that the maintainer, Thiago, also accepted:

if (thread() != QThread::currentThread())
{
    QMetaObject::invokeMethod(this, "exitLoop", Qt::QueuedConnection);
    return;
}

It is a bit of shame really that this did not go in before 5.4. Having said that, this will be fixed for Qt 5.4 as the change was merged:

Make QTestEventLoop::exitLoop() thread-safe

Quickie answered 6/12, 2014 at 21:6 Comment(2)
I read the links. Especially the ML disucssion in interesting. I am unsure if I understood the reasons completely why they decided to fix the problem on the QTestEventLoop side, not on the QSignalSpy connect side. Both solutions would have solved the problem, wouldn't they?Stome
Because the maintainer liked that way.Migration
O
1

In order to make QSignalSpy work reliably across threads I use the following approach: I move the spy to the worker thread and I re-implement the wait function as follows:

#include <QSignalSpy>
#include <QTime>
struct ThreadsafeQSignalSpy : QSignalSpy
{
    template <typename Func>
    ThreadsafeQSignalSpy(const typename QtPrivate::FunctionPointer<Func>::Object *obj, Func signal0)
        : QSignalSpy(obj, signal0)
    {}

    bool wait(int timeout)
    {
        auto origCount(count());
        QTime timer;
        timer.start();

        while (count() <= origCount && timer.elapsed() < timeout)
            QCoreApplication::instance()->processEvents(QEventLoop::AllEvents, timeout/10);
        return count() > origCount;
    }
};


void TestSuite::testFunction()
{
    QThread thread;
    ...
    ThreadsafeQSignalSpy spy;
    spy.moveToThread(thread);
    /// now wait should work
    ...
    QVERIFY(spy.wait(1000));
}
Olmos answered 25/1, 2016 at 17:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.