QSignalSpy不能与线程一起使用。

6
我写了一个执行工作对象的线程。一切都运行良好。信号也按照应该的方式被发射出来。当然,我注意到了与线程/对象亲和性有关的常见错误。
今天,我为这些工人/线程编写了自动化模块测试。我创建了一个QSignalSpy来等待由工人对象(已移至线程)发出的信号,如下所示:
QSignalSpy spy(worker, SIGNAL(Success()));
thread.ExecuteWorker();
QVERIFY(spy.wait()); // Error in this line

我在标记的行中遇到了一个众所周知的错误:

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

起初我以为是自己的错误,因为wait()函数中的代码在错误的线程中被执行。后来我在QSignalSpy的实现代码中发现了下面这段:

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

这显然意味着 QSignalSpy 总是使用 DirectConnection,无法用于监视生存在不同线程中的对象的信号。
为什么在Qt5.3中会这样编程?这是一个错误还是故意的行为?如何解决这个限制?
2个回答

6
很不幸,这是一个长期存在的问题,已经有六年多了: QSignalSpy crashes if signal is emitted from worker thread 我几年前在Qt贡献者峰会上遇见了Jason,但之后他离开了诺基亚,因为诺基亚关闭了他所在的布里斯班办事处。此后,这个Qt测试模块中没有太多的贡献,令人遗憾。
最近在邮件列表中也有更多关于它的讨论: Why is QSignalSpy using Qt::DirectConnection? Roland提出的解决方案得到了维护者Thiago的认可:
if (thread() != QThread::currentThread())
{
    QMetaObject::invokeMethod(this, "exitLoop", Qt::QueuedConnection);
    return;
}

这真是有点遗憾,因为在5.4之前没有实现。不过话说回来,这个问题将在Qt 5.4中得到修复,因为已经合并了以下更改:

使QTestEventLoop::exitLoop()线程安全


我阅读了这些链接,特别是机器学习讨论部分很有趣。我不确定我是否完全理解了他们为什么决定在 QTestEventLoop 方面解决问题,而不是在 QSignalSpy 连接方面解决问题的原因。两种解决方案都可以解决问题,不是吗? - Silicomancer
因为维护者喜欢这种方式。 - László Papp

1
为了使QSignalSpy跨线程可靠工作,我采用以下方法:将spy移动到工作线程,并重新实现wait函数如下:
#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));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接