在 QThread::run 中间调用了一个插槽

Posted

技术标签:

【中文标题】在 QThread::run 中间调用了一个插槽【英文标题】:A slot gets called in the middle of QThread::run 【发布时间】:2013-06-10 17:59:29 【问题描述】:

我正在修改一个多线程 Qt 应用程序并且遇到了无法预料的行为。

我有一个继承自 QThread 的类 WorkerThread,其中的方法 run() 做了一些工作。 WorkerThread 对象的插槽handleSuccess() 连接到来自另一个主线程的信号,该信号是响应来自服务器的异步传入连接而发出的。据我了解,当run() 正在运行一个流控制不在事件循环的exec() 中时,无法调用该插槽。相反,我在应用程序的日志中看到该线程正在run() 的中间点进行工作,然后立即进入插槽而无需做任何准备。

class WorkerThread : public QThread 
    Q_OBJECT
public:
    WorkerThread()  moveToThread(this); /* further initialization */ 
    void run();
    void foo();

public slots:
    void handleSuccess(const QByteArray &);
    void shutdown();

private:
    QMutex mutex;
    MyResource resource;
    volatile bool mShutdown;

/* ... other declarations */
;

void WorkerThread::foo() 
    QMutexLocker locker(&mutex);
    /* Working with the guarded resource */


void WorkerThread::run() 
    QEventLoop signalWaiterEventLoop;
    connect(this, SIGNAL(terminated()), &signalWaiterEventLoop, SLOT(quit()), Qt::QueuedConnection);
    connect(this, SIGNAL(shutdownThread()), &signalWaiterEventLoop, SLOT(quit()), Qt::QueuedConnection);
    connect(this, SIGNAL(dbChanged()), &signalWaiterEventLoop, SLOT(quit()), Qt::QueuedConnection);

    while (!mShutdown) 
        signalWaiterEventLoop.exec();
        if (mShutdown) break;
        /* ... useful payload */
        foo();
        /* ... another payload */
    



void WorkerThread::handleSuccess(const QByteArray & data) 
            log(Q_FUNC_INFO, __LINE__, __FILE__);
    QMutexLocker locker(&mutex);
    /* processing data */


void WorkerThread::shutdown()

    mShutdown = true;
    emit shutdownThread();

在主线程中创建并初始化对象:

void SomeClass::init() 
    /* ... */
    mWorker = new WorkerThread();
    connect(connect(mProtocol, SIGNAL(dataReceived(QByteArray)), mWorker, SLOT(processSuccess(QByteArray)),
                Qt::QueuedConnection));    
    mWorker->start();
    /* ... */

当从run() 内部调用foo(),锁定互斥体,然后将流控制传递给handleSuccess() 时,我无法看到任何原因,最后handleSuccess() 尝试锁定互斥锁导致死锁。我会强调所有这些都发生在一个线程中,我已经记录了。事实是,误差是稳定的,每次都出现在同一个地方。很明显我没有考虑到什么,但究竟是什么?

更新 我已经将WorkerThread 改写成Worker: public QObject 并且以前的run() 变成了公共槽handleNewArrivals(),它在线程的事件循环中被调用以响应信号。尽管如此,问题仍然存在:另一个插槽在handleNewArrivals() 的中间执行。打断的地方稳定,就在这里(求evaluateTo()):

    bool hasContent = false;
    QByteArray outData;
    QBuffer inputBuffer(&data), outputBuffer(&outData);
    inputBuffer.open(QIODevice::ReadOnly);
    outputBuffer.open(QIODevice::WriteOnly|QIODevice::Append);

    QXmlQuery xmlQuery;
    xmlQuery.bindVariable("inputDocument", &inputBuffer);
    xmlQuery.setQuery("doc($inputDocument)/commands//*");

    QXmlSerializer serializer(xmlQuery, &outputBuffer);
    log(Q_FUNC_INFO, __LINE__, __FILE__);
    // the log line above appears, then after 1 ms the line
    // from handleSuccess() appears successively.
    xmlQuery.evaluateTo(&serializer);

    // this line is never shown
    log(Q_FUNC_INFO, __LINE__, __FILE__);
    QXmlStreamReader xmlReader(outData);
    while (!xmlReader.atEnd()) 
        xmlReader.readNext();
        if (xmlReader.tokenType() == QXmlStreamReader::Invalid) 
            ufo::logT(this) << tr("Invalid token: %1").arg(xmlReader.errorString());
            continue;
        

        if (!xmlReader.isStartDocument() && !xmlReader.isEndDocument()) 
            xmlWriterDb.writeCurrentToken(xmlReader);
            hasContent = true;
        
    
    ufo::logT(this) << tr("Selected data from payment, hasContent = %1").arg(hasContent?"true":"false");
    inputBuffer.close();
    outputBuffer.close();

QXmlQuery::evaluateTo 和 QXmlSerializer 会导致这种跳转?似乎有一个异常或类似unix的信号(该软件在Windows下运行,mingw32)或其他东西,尽管包装evaluateto()或try-catch中的整个slot什么都没有。

【问题讨论】:

现在没有时间给出完整的答案,但我怀疑继承 QThread 并覆盖 run() 不是适合您的情况的正确线程模型,但您应该使用 QThread 作为线程而不是将代码直接放入其中。这将为您提供正确的事件分派,并且您不需要启动自己的自定义事件循环的笨拙方法。见:***.com/a/13142366/856199 你没有在至少一个地方向我们展示真实的代码(这可能很重要)。您对工作对象的connect() 看起来像connect(connect(mProtocol, SIGNAL(dataReceived(QByteArray)), mWorker, SLOT(processSuccess(QByteArray)),Qt::QueuedConnection));,它至少有两个问题——嵌套的connect() 调用并且没有名为processSuccess() 的插槽。 对,双连接是错误类型,槽位是handleSuccess: connect(mProtocol, SIGNAL(dataReceived(QByteArray)), mWorker, SLOT(handleSuccess(QByteArray)), Qt::排队连接));我不确定我是否有权在此处发布真实代码。 【参考方案1】:

我想你正在被这种行为所困扰documented for QXmlQuery

事件处理

当 QXmlQuery 访问资源时(例如,调用 fn:doc() 来加载一个 文件,或通过绑定变量访问设备),事件循环是 used,这意味着将处理事件。避免处理事件 当 QXmlQuery 访问资源时,在一个 单独的线程。

如果dataReceived() 信号已排队等待handleSuccess() 插槽,则当QXmlQuery 使用事件循环时,将处理该事件。

【讨论】:

谢谢你,迈克尔,它看起来和我得到的完全一样。我必须想办法解决。 在我将 QXmlQuery 和相关调用移到另一个线程后,一切都运行良好。【参考方案2】:

所有QThread 对象都在GUI 线程中创建并属于它。这就是为什么在 QThread 派生类中定义的任何插槽都将在主 GUI 线程中执行,而不是在对象所代表的线程中执行。

如果您希望此槽在另一个线程中执行,最简单的解决方案是使用 moveToThread(this)WorkerThread 对象移动到此线程。

但这不是最好的决定。考虑到您发布的代码,我认为您没有理由将 QThread 子类化。默认的 QThread 实现已经满足您的需求。您应该删除 WorkerThread 类,创建另一个 QObject 派生类,例如WorkerObject,然后使用QObject::moveToThread 将其移动到新线程。之后,该对象的任何槽都将在另一个线程中执行。

【讨论】:

我会尝试将 WorkerThread 的逻辑分离到非 QThread 类中,但我仍然不知道为什么插槽被如此侵入地调用,因此我不确定这种行为不会重蹈覆辙。 run() 在 WorkerThread 代表的线程中执行。 handleSuccess 在主线程中执行。这就是为什么它们可以随时同时调用。 它们是从同一个线程调用的,即具有相同的线程 ID。这就是为什么我会陷入僵局。如果互斥锁是从不同线程中获取的,那么第二个线程将只是等待第一个线程。事实上,即使我注释掉handleSuccess 的整个主体,执行也永远不会返回到run,因此永远不会将互斥锁锁定在里面。这似乎是一些例外,但在 try-catch 中包装 run() 显示没有捕获。 在我看来,您已经创建了新的 QEventLoop 并调用了它的 exec() 方法。所以你的新事件循环正在调用你的插槽。调用槽是 QEventLoop 所做的。这并不奇怪。为什么你需要创建新的事件循环?你不应该那样做。 上面,Michael Burr 建议了隐式运行事件循环的地方。

以上是关于在 QThread::run 中间调用了一个插槽的主要内容,如果未能解决你的问题,请参考以下文章

Qt 多线程和网络编程学习

Qt新建线程的方法(四种办法,很详细,有截图)

QT的多线程使用

如果我只使用插槽,是不是需要使用 Q_OBJECT 宏?

从 QThread 影响 QDialog

我可以在属于主线程的 QThread 中使用 waitForReadyRead 吗?