2017-01-01 43 views
10

根據最新的C++ TS:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4628.pdf,基於對C#異步/等待語言支持的理解,我想知道什麼是「執行上下文」(從C# )的C++協程?C++ 1z協同線程上下文和協程調度

我在Visual C++ 2017 RC中的簡單測試代碼揭示了協程似乎總是在一個線程池線程上執行,並且很少控制應用程序開發者在其上可以執行協程的線程上下文。應用程序是否可以強制所有的協同程序(使用編譯器生成的狀態機代碼)僅在主線程上執行,沒有涉及任何線程池線程?

在C#中,SynchronizationContext是指定所有協程「半部」(編譯器生成的狀態機代碼)將被髮布和執行的「上下文」的一種方式,如本文所示:https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/,而當前協程實現在Visual C++ 2017 RC中似乎總是依賴於併發運行時,它默認在線程池線程上執行生成的狀態機代碼。是否有類似的同步上下文概念,用戶應用程序可以使用它來將協程執行綁定到特定的線程?

此外,在Visual C++ 2017 RC中實現的協程的當前默認「調度程序」行爲是什麼?即1)如何準確指定等待條件? 2)當等待條件滿足時,誰調用暫停協程的「下半部分」?我對C#中的任務調度的(幼稚)推測是,C#「純粹通過任務延續」實現「等待條件 - 等待條件由TaskCompletionSource擁有的任務合成,並且需要等待的任何代碼邏輯將被鏈接作爲它的延續,所以如果等待條件得到滿足,例如如果從低級網絡處理程序收到完整的消息,它會執行TaskCompletionSource.SetValue,它將底層任務轉換爲完成狀態,從而有效地允許鏈式繼續邏輯開始執行(將任務從先前創建的狀態) - 在C++協同程序中,我推測std :: future和std :: promise將用作類似的機制(std :: future是任務,而std :: promise是TaskCompletionSource,而用法也是令人驚訝的相似!) - C++協程調度程序(如果有的話)也依賴於某種類似的機制來執行該行爲?我編寫了一個非常簡單但非常強大的awaitable抽象,它支持單線程和協作式多任務處理,並且具有一個簡單的基於thread_local的調度器,它可以在線程協程啓動。該代碼可以從這個GitHub庫中找到:https://github.com/llint/Awaitable

Awaitable是它保持正確調用的嵌套級別排序的方式組合的,並且它的功能基本收益,定時等待,並設定從別的地方準備好,也很複雜(如無限循環協程,只有在某些事件發生時纔會喚醒),編程模型緊隨基於C#Task的異步/等待模式。請隨時提供您的反饋。

+1

偉大的問題!至於C#中的任務調度,其全部在[github](https://github.com/dotnet/coreclr/tree/master/src/mscorlib/src/System/Threading/Tasks)處打開,提供了一些很好的見解。至於C++,其中一個提案[n4286](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4286.pdf)(在提交到草案之前)涵蓋了演示實現過度提振的未來,但它似乎'誰'調用延續真的會依賴於impl –

+0

我認爲你的問題同樣適用於線程/上下文[future :: then](http://en.cppreference.com/w/) cpp/experimental/future/then)會被調用,即undefined¯\ _(ツ)_ /¯ –

+1

感謝您的評論。然而,對於「誰」調用繼續,我會推測應該有一些建議的標準措辭或設施來支持實現定製的單線程協程調度程序 - 例如,如果一切都將在主線程上執行,則應在主線程上調用主調度器循環(或滴答),以便在所有計劃的「任務一半」上執行 - 或「主」線程可以是我選擇的更多不可控的線程池線程的任何線程。 – Dejavu

回答

9

與此相反!

C++協程完全是關於控制。這裏的關鍵是
void await_suspend(std::experimental::coroutine_handle<> handle) 函數。

evey co_await預計等待類型。簡而言之,等待類型是提供這三種功能的類型:

  1. bool await_ready() - 程序是否應該暫停執行協程?
  2. void await_suspend(handle) - 程序向您傳遞該協程框架的延續上下文。如果激活了句柄(例如,通過調用句柄提供的operator() - 當前線程立即恢復協程)。
  3. T await_resume() - 告訴恢復協程時如何恢復協程的線程以及從co_await返回的內容。

,所以當你在awaitable呼叫類型co_await,程序將會詢問awaitable如果協同程序應暫停(如await_ready返回false),如果是的話 - 你得到一個協程手柄,你可以做任何你喜歡的。

例如,您可以將協程句柄傳遞給線程池。在這種情況下,線程池線程將恢復協程。

您可以將協程句柄傳遞給簡單的std::thread - 您的自己的創建線程將恢復協程。

您可以將協程句柄附加到派生類OVERLAPPED,並在異步IO完成時恢復協程。

正如你所看到的 - 你可以通過管理在await_suspend中傳遞的協程句柄來控制協程暫停和恢復的位置和時間。沒有「默認調度程序」 - 如何實現您的等待類型將決定如何調度協程。

那麼,VC++會發生什麼?不幸的是,std::future仍然沒有then函數,所以你不能通過協程句柄到std::future。如果您在std::future上等待 - 程序只會打開一個新線程。看看由future頭部中給出的源代碼:

template<class _Ty> 
    void await_suspend(future<_Ty>& _Fut, 
     experimental::coroutine_handle<> _ResumeCb) 
    { // change to .then when future gets .then 
    thread _WaitingThread([&_Fut, _ResumeCb]{ 
     _Fut.wait(); 
     _ResumeCb(); 
    }); 
    _WaitingThread.detach(); 
    } 

那麼,爲什麼你看到,如果協程在常規std::thread推出一個Win32線程池線程?那是因爲它不是協程。 std::async在幕後致電concurrency::create_task。在默認情況下,在win32線程池下啓動一個concurrency::task。畢竟,std::async的全部目的是在另一個線程中啓動可調用。

+0

非常好!這個答案似乎澄清了協程的線程/執行環境的奧祕。看來雖然爲了支持future.then(又名。任務延續),似乎仍然需要一個任務調度程序,這個延續只會被調度運行(也許在同一個線程上下文中,具體取決於具體的調度程序噹噹前任務/未來完成時 - VC++方法解決了使用任務調度程序的問題,因爲它依賴於阻塞等待不同線程,然後在該線程上恢復協程。 – Dejavu

+0

'告訴恢復協程的線程恢復協程時要做什麼,以及從co_await返回什麼。'雖然我不確定它是否完全準確,但恢復會發生在「暫停恢復點」及其操作符()() coroutine_handle告訴「該怎麼做」。 'await_resume'只是一個獲取最終值的鉤子,在某些情況下可能爲空 –