【发布时间】:2015-02-04 21:53:18
【问题描述】:
我在我的应用程序中构建了一个基于 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);
【问题讨论】:
-
为什么要使用 QtConcurrent 来调度网络请求?您能否详细说明传递给
run的 lambda 内部发生了什么?我感觉里面有问题。 -
我想要一种简单的方法来发出多个网络请求,然后只看结果,即使用未来。
QtConcurrent似乎很适合。使用有关 lambda 的更多详细信息更新了问题。 -
但是 QNetworkAccessManager 本质上是完全异步的。它们提供异步信号来通知正在发生的事情。发出请求并“稍后”检查它们有什么问题?
-
另外我还特意询问了在 lambda 中做了什么,因为 QObjects 的线程亲和性发挥了作用。
-
QNetworkAccessManager 是完全异步的,但只能通过信号/插槽,这并不是在所有情况下工作的最佳方式。我的算法将计算各种东西,一些需要网络数据,另一些需要来自数据库的东西。我想要一种简单的方法来表示“获取供以后使用的东西”。 QFuture 似乎是一种很好的方法来抽象数据的来源,并在我以后需要时提供非常方便的访问。
标签: qt qtnetwork qtconcurrent qt5.4