在 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 中间调用了一个插槽的主要内容,如果未能解决你的问题,请参考以下文章