Каким образом QtConcurrent::run запускается в основном потоке?

Я построил в своем приложении асинхронный сетевой фасад на основе 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: лямбда, которая выполняется, просто выполняет 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);

1 ответ

Решение

Видимо это по замыслу. Из 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() возвращает 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.
*/

Итак, что мы получим, если QRunnable созданный внутри QtConcurrent::run() не был снят с 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.
}

Я получил свой дизайн (получить будущее из QNetowrkAccessManager получить) работая с помощью std::future а также std::async,

Другие вопросы по тегам