1

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

我有一个从 QThread 继承的 WorkerThread 类,其中的方法run()在做一些工作。handleSuccess()WorkerThread 对象的一个​​槽连接到来自另一个主线程的信号,该信号是响应来自服务器的异步传入连接而发出的。据我了解,在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();
    /* ... */
}

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

更新 我已经重写了WorkerThreadintoWorker: public QObject并且前者run()变成了 public slot handleNewArrivals(),它在线程的事件循环中调用以响应信号。尽管如此,问题仍然存在:另一个插槽在handleNewArrivals(). 中断的地方是稳定的,这里是(seek 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中的整个槽体没有给出任何东西。

4

2 回答 2

2

我想你被记录在案的这种行为咬伤了QXmlQuery

事件处理

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

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

于 2013-06-11T07:44:04.830 回答
0

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

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

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

于 2013-06-10T19:09:05.467 回答