QtConcurrent::run 如何在主线程上结束?
How does QtConcurrent::run wind up on main thread?
我在我的应用程序中构建了一个基于 QFuture 的异步网络外观。大致是这样的:
namespace NetworkFacade {
QByteArray syncGet(const QUrl& url) {
QEventLoop l;
QByteArray rc;
get(url, [&](const QByteArray& ba) {
rc = ba;
l.quit();
});
l.exec();
return rc;
}
void get(const QUrl& url, const std::function<void (const QByteArray&)>& handler) {
QPointer<QNetworkAccessManager> m = new QNetworkAccessManager;
QObject::connect(m, &QNetworkAccessManager::finished, [=, &m](QNetworkReply *r) {
QByteArray ba;
if (r && r -> error() == QNetworkReply::NoError)
ba = r -> readAll();
m.clear();
if (handler)
handler(ba);
});
m -> get(QNetworkRequest(url));
}
}
我有一个 QTimer
触发主线程上的调用,执行以下操作(显然已简化):
foreach(Request r, requests) {
futures.push_back(get(r));
}
foreach(QFuture<SomeType> f, futures) {
f.waitForFinished();
[do stuff with f.result()]
}
我的假设是 waitForFinished()
会在后台线程执行我的网络请求时阻塞主线程。相反,我收到 qFatal
错误:
ASSERT: "m_blockedRunLoopTimer == m_runLoopTimer" in file eventdispatchers/qeventdispatcher_cf.mm, line 237
在堆栈跟踪中,我在主线程上看到我的 waitForFinished()
,但后来我没有被阻止,而是看到(从下往上阅读):
com.myapp 0x0008b669 QEventDispatcherCoreFoundation::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 1753
com.myapp 0x000643d7 QIOSEventDispatcher::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 823
com.myapp 0x0130e3c7 QEventLoop::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 119
com.myapp 0x0130e5fb QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) + 539
com.myapp 0x0003a550 NetworkFacade::syncGet(QUrl const&) + 208
com.myapp 0x00037ed1 QtConcurrent::StoredFunctorCall0<std::__1::shared_ptr<QuoteFacade::Quote>, QuoteFacade::closingQuote(QString const&, QDate const&)::$_0>::runFunctor() + 49
com.myapp 0x00038967 QtConcurrent::RunFunctionTask<std::__1::shared_ptr<QuoteFacade::Quote> >::run() + 87
com.myapp 0x00038abc non-virtual thunk to QtConcurrent::RunFunctionTask<std::__1::shared_ptr<QuoteFacade::Quote> >::run() + 28
com.myapp 0x010dc40f QThreadPoolPrivate::stealRunnable(QRunnable*) + 431
com.myapp 0x010d0c35 QFutureInterfaceBase::waitForFinished() + 165
因此,我并发任务不是等待 QFuture
获取值,而是在主线程上发出。这会导致我上面概述的 get()
函数被调用,它会侦听 QEventLoop
上的事件。同时,QTimer
再次触发,我从上面得到断言。
我是不是做错了什么,或者 QtConcurrent::run
可以导致控制返回主线程是否完全有效?
=== 更新 1
@peppe:正在执行的 lambda 只是执行 HTTP GET 并生成将 JSON 响应解析为 SomeType
对象。通过 QFuture
.
访问的结果
=== 更新 2
显然这是设计使然。来自 qfutureinterface.cpp
从 Qt 5.4.0 行 293-295:
// To avoid deadlocks and reduce the number of threads used, try to
// run the runnable in the current thread.
d->pool()->d_func()->stealRunnable(d->runnable);
显然这是设计使然。来自 Qt 5.4.0 第 293-295 行的 qfutureinterface.cpp:
// To avoid deadlocks and reduce the number of threads used, try to
// run the runnable in the current thread.
d->pool()->d_func()->stealRunnable(d->runnable);
QtConcurrent::run()
returns a QFuture
使用 QFutureInterface
实现。 QFutureInterface
在 waitForFinished()
和 waitForResult()
中都包含该代码。
stealRunnable
是 QThreadPool
的一个未记录的私有方法。在 headerdoc 中这样描述:
/*!
\internal
Searches for \a runnable in the queue, removes it from the queue and
runs it if found. This function does not return until the runnable
has completed.
*/
所以我们最后得到的是,如果 QtConcurrent::run()
在内部创建的 QRunnable
没有从分配给它的任何 QThreadPool
中取出,然后调用 waitForFinished
或 waitForResult
将导致它在当前线程上 运行(即不同时。)
这意味着像这样的代码(以及我在问题中所做的)可能会以神秘的方式失败:
foreach (FuncPossiblyTriggeringQEvents fn, tasks) {
futures.push_back(QtConcurrent::run(fn));
}
foreach (QFuture<> f, futures) {
f.waitForFinished(); // Some of my tasks will run in this thread, not concurrently.
}
我通过使用 std::future
和 std::async
.
实现了我的设计(从 QNetowrkAccessManager
得到的未来)
我在我的应用程序中构建了一个基于 QFuture 的异步网络外观。大致是这样的:
namespace NetworkFacade {
QByteArray syncGet(const QUrl& url) {
QEventLoop l;
QByteArray rc;
get(url, [&](const QByteArray& ba) {
rc = ba;
l.quit();
});
l.exec();
return rc;
}
void get(const QUrl& url, const std::function<void (const QByteArray&)>& handler) {
QPointer<QNetworkAccessManager> m = new QNetworkAccessManager;
QObject::connect(m, &QNetworkAccessManager::finished, [=, &m](QNetworkReply *r) {
QByteArray ba;
if (r && r -> error() == QNetworkReply::NoError)
ba = r -> readAll();
m.clear();
if (handler)
handler(ba);
});
m -> get(QNetworkRequest(url));
}
}
我有一个 QTimer
触发主线程上的调用,执行以下操作(显然已简化):
foreach(Request r, requests) {
futures.push_back(get(r));
}
foreach(QFuture<SomeType> f, futures) {
f.waitForFinished();
[do stuff with f.result()]
}
我的假设是 waitForFinished()
会在后台线程执行我的网络请求时阻塞主线程。相反,我收到 qFatal
错误:
ASSERT: "m_blockedRunLoopTimer == m_runLoopTimer" in file eventdispatchers/qeventdispatcher_cf.mm, line 237
在堆栈跟踪中,我在主线程上看到我的 waitForFinished()
,但后来我没有被阻止,而是看到(从下往上阅读):
com.myapp 0x0008b669 QEventDispatcherCoreFoundation::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 1753
com.myapp 0x000643d7 QIOSEventDispatcher::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 823
com.myapp 0x0130e3c7 QEventLoop::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 119
com.myapp 0x0130e5fb QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) + 539
com.myapp 0x0003a550 NetworkFacade::syncGet(QUrl const&) + 208
com.myapp 0x00037ed1 QtConcurrent::StoredFunctorCall0<std::__1::shared_ptr<QuoteFacade::Quote>, QuoteFacade::closingQuote(QString const&, QDate const&)::$_0>::runFunctor() + 49
com.myapp 0x00038967 QtConcurrent::RunFunctionTask<std::__1::shared_ptr<QuoteFacade::Quote> >::run() + 87
com.myapp 0x00038abc non-virtual thunk to QtConcurrent::RunFunctionTask<std::__1::shared_ptr<QuoteFacade::Quote> >::run() + 28
com.myapp 0x010dc40f QThreadPoolPrivate::stealRunnable(QRunnable*) + 431
com.myapp 0x010d0c35 QFutureInterfaceBase::waitForFinished() + 165
因此,我并发任务不是等待 QFuture
获取值,而是在主线程上发出。这会导致我上面概述的 get()
函数被调用,它会侦听 QEventLoop
上的事件。同时,QTimer
再次触发,我从上面得到断言。
我是不是做错了什么,或者 QtConcurrent::run
可以导致控制返回主线程是否完全有效?
=== 更新 1
@peppe:正在执行的 lambda 只是执行 HTTP GET 并生成将 JSON 响应解析为 SomeType
对象。通过 QFuture
.
=== 更新 2
显然这是设计使然。来自 qfutureinterface.cpp
从 Qt 5.4.0 行 293-295:
// To avoid deadlocks and reduce the number of threads used, try to
// run the runnable in the current thread.
d->pool()->d_func()->stealRunnable(d->runnable);
显然这是设计使然。来自 Qt 5.4.0 第 293-295 行的 qfutureinterface.cpp:
// To avoid deadlocks and reduce the number of threads used, try to
// run the runnable in the current thread.
d->pool()->d_func()->stealRunnable(d->runnable);
QtConcurrent::run()
returns a QFuture
使用 QFutureInterface
实现。 QFutureInterface
在 waitForFinished()
和 waitForResult()
中都包含该代码。
stealRunnable
是 QThreadPool
的一个未记录的私有方法。在 headerdoc 中这样描述:
/*!
\internal
Searches for \a runnable in the queue, removes it from the queue and
runs it if found. This function does not return until the runnable
has completed.
*/
所以我们最后得到的是,如果 QtConcurrent::run()
在内部创建的 QRunnable
没有从分配给它的任何 QThreadPool
中取出,然后调用 waitForFinished
或 waitForResult
将导致它在当前线程上 运行(即不同时。)
这意味着像这样的代码(以及我在问题中所做的)可能会以神秘的方式失败:
foreach (FuncPossiblyTriggeringQEvents fn, tasks) {
futures.push_back(QtConcurrent::run(fn));
}
foreach (QFuture<> f, futures) {
f.waitForFinished(); // Some of my tasks will run in this thread, not concurrently.
}
我通过使用 std::future
和 std::async
.
QNetowrkAccessManager
得到的未来)