QSignalSpy cannot be used with streams
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 and object proximity.
Today I wrote an automated unit test for these workers / threads. I created a QSignalSpy to wait for a signal that is emitted by the worker object (which has been pushed onto the thread) like this:
QSignalSpy spy(worker, SIGNAL(Success()));
thread.ExecuteWorker();
QVERIFY(spy.wait()); // Error in this line
I am getting the known error on the marked line:
QObject::killTimer: timers cannot be stopped from another thread
I first encountered an error on my side because some code in wait () was executed on the wrong thread. Then I found the following code in the QSignalSpy implementation:
if (!QMetaObject::connect(obj, sigIndex, this, memberOffset, Qt::DirectConnection, 0))
{
qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect.");
return;
}
This means that QSignalSpy uses DirectConnection all the time and cannot be used to monitor signals from objects living in different threads.
Why did they program it this way in Qt5.3? Is this a bug or is this intended behavior? How can I get around this limitation?
source to share
This is, unfortunately, a long-standing problem, more than six years old, to be fair:
QSignalSpy crashes if signal comes from worker thread
I met Jason at the Qt Contributor Summit a couple of years ago, but then he left Nokia a bit after that as Nokia closed the Brisbane office where he worked. After that, not much has been done in this Qt test unit, unfortunately.
There have been more discussions on the mailing list lately:
Why does QSignalSpy use Qt :: DirectConnection?
The solution offered by Roland was that the attendant, Thiago, also accepted:
if (thread() != QThread::currentThread())
{
QMetaObject::invokeMethod(this, "exitLoop", Qt::QueuedConnection);
return;
}
It's a bit of a shame that this didn't happen until 5.4. Having said that, it will be fixed for Qt 5.4 when the change was merged:
source to share
To get QSignalSpy to work reliably on threads, I take the following approach, I move the spy into a worker thread and I re-implement the wait function like this:
#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));
}
source to share