Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QSignalSpy can not be used with threads

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?

like image 492
Silicomancer Avatar asked Dec 06 '14 20:12

Silicomancer


2 Answers

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

like image 52
lpapp Avatar answered Sep 19 '22 06:09

lpapp


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));
}
like image 25
hawk78 Avatar answered Sep 19 '22 06:09

hawk78