2015-02-04 12 views
0

저는 응용 프로그램에 QFuture 기반 비동기 네트워킹 외관을 구축했습니다. 대략은 다음과 같이 작동QtConcurrent ::는 메인 스레드에서 어떻게 실행됩니까?

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() 주를 차단하는 것이라고했다 배경 스레드가 내 네트워크 요청을 실행하는 동안 그래서

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 

: 나는 (아래에서 위로 읽음)를 참조 차단되는 대신 나는 내 waitForFinished() 주 스레드에서 참조 스택 추적에서

ASSERT: "m_blockedRunLoopTimer == m_runLoopTimer" in file eventdispatchers/qeventdispatcher_cf.mm, line 237 

하지만, 대신 나는 qFatal 오류 QFuture 값을 얻으려고 기다리는 대신 내 생각에 동시 작업은 주 스레드에서 실행됩니다. 이로 인해 위에서 설명한 get() 함수가 호출되어 QEventLoop의 이벤트를 수신합니다. 그 사이에, QTimer는 다시 발화한다. 그리고 나는 위의 주장을 얻는다.

내가 뭔가 잘못하고 있는거야, 아니면 QtConcurrent::run이 주 스레드로 다시 돌아갈 수 있다고 완벽하게 유효합니까?

=== 업데이트 1

@peppe : 람다 실행 중이 단순히 HTTP GET 수행하고 SomeType 객체로 JSON 응답을 파싱하여 생성한다. 결과는 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); 
+0

QtConcurrent를 사용하여 네트워크 요청을 디스패치하는 이유는 무엇입니까? 그리고 '달리는'람다 안에서 무슨 일이 일어나고 있는지 자세히 설명해 주시겠습니까? 그곳에 뭔가 잘못된 점이 있습니다. – peppe

+0

여러 네트워크 요청을 발행하고 결과를 나중에 보거나, 미래를 사용하는 간단한 방법을 원합니다. 'QtConcurrent'는 그게 딱 맞는 것 같았습니다. 질문을 람다에 대한 세부 정보로 업데이트했습니다. – Tim

+0

그러나 QNetworkAccessManager는 완전히 비동기입니다. 진행 상황을 알려주는 비동기 신호를 제공합니다. 요청을 발행하고 "나중에"검사 할 때의 문제점은 무엇입니까? – peppe

답변

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()QFutureInterface를 사용하여 구현하는 QFuture을 반환합니다. QFutureInterface에는 해당 코드가 waitForFinished()waitForResult()에 모두 포함되어 있습니다.

stealRunnable은 문서화되지 않은 개인 메서드 QThreadPool입니다. 그것은 headerdoc에 thusly 히 설명 :

/*! 
    \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를 호출에 할당 된 대기열에서 제거되지 않은됩니다와 바람 무엇을 현재 스레드에서 실행되도록 (즉, 동시에 수행하지 마십시오.)

같은 코드 (그리고 제가 질문에서했던) 신비한 방법으로 실패 할 수 있습니다를 의미한다 : 나는 std::future를 사용하여 작업 (A QNetowrkAccessManager GET 중 미래를 받고) 내 디자인을 가지고

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::async.

+0

이 비트는 나를 부수고 꽤 부서진 것처럼 보입니다 ... 적어도이 행동을 해제 할 수 있다면 좋을 것입니다. 특히 함수를 실행할 스레드 풀을 명시 적으로 지정할 때 직관적이지는 않지만 직관적입니다. 대신 호출 스레드에서 실행됩니다. 예 : QtConcurrent :: run (& myThreadPool, fn) – Ben

+0

네, 귀찮습니다 . 'std :: async'를 대신 사용하고 있는데,'C++ 11'을 사용할 수 있다면 훨씬 더 좋습니다. – Tim

+0

위의 예제를 std :: async를 사용하여 변환 한 방법을 보여주는 코드 스 니펫을 공유 하시겠습니까? 나는 당신의 대답을 본 후에 std :: async에서 조금 보았다. 그러나 나의 유스 케이스에서는 잘 작동하지 않을 것이다. 특정 장기 스레드를 실행하기 위해 비동기 작업을 예약 할 필요가 있습니다 ... (최대 스레드 크기를 1로 설정하고 무한 스레드 만료 날짜를 사용하여 QThreadPool을 사용하여 허우적하게이 시나리오를 수행 중입니다.) .) 당신이 std :: async로 이것을 달성하는 방법을 알게된다면 나는 약간의 포인터를 좋아할 것이다 ... – Ben