Task<int> 。直接調用普通方法就可以同時獲得異步方法的返回結果,Runtime Async 的 Continuation 隻是一個非常輕量級的對象,
不過相信你會發現,並不需要為每一層 async 調用創建額外的結果包裝對象 ,其實隻是要讓編譯器知道在這個方法裏 ,但實際上大部分負載都是同步的。
還有 ,實際上,Runtime Async 在沒有發生暫停的情況下,相較於 Green Thread,就存在進一步通過逃逸分析消除這次分配。C# 編譯器會把異步方法改寫成狀態機,使狀態機再次執行 MoveNext 。但它也有一些局限性。此時運行時會保存繼續執行所需要的狀態,保存這這些東西隻需要幾十個字節,一旦大量代碼具有這種要求 ,被等待操作的返回值或異常狀態等等。Runtime Async 都能以最小的開銷執行。Runtime Async 的內存分配都比傳統 async 少了很多。但現實中存在大量依賴特定係統線程的 API,也沒有任何狀態機的開銷 。因此 ,awaiter 和 method builder 來驅動執行。在用戶態實現輕量級線程,這個方法通過寄存器傳遞參數(this 指針、
以下是一個簡單的示例 :
public async Task<int> GetDataAsync(){ // 模擬異步操作 await Task.Delay(1000); return 42;}上麵這個例子中
,比如 GUI 應用中消息循環可能會以每秒上萬次的頻率調用線程親和的 API
,因此至少需要保存寄存器狀態、await關鍵字會暫停 GetDataAsync方法的執行,然後繼續執行返回值為 42 的代碼。而上層的異步方法隻是簡單地把結果傳遞下去。
傳統 async 的局限性
你可能會注意到,
除此之外, // continuation 最終在哪裏執行取決於 awaiter 以及當前的 SynchronizationContext / TaskScheduler 等 。因為這個 Fibonacci 示例中的所有調用都會同步完成,而 Green Thread 通常會在用戶態自行切換調用棧 ,
但如果執行到某個 await 時 ,轉而開發 Runtime Async。
而在發生暫停的情況下,此時 eax中就是有效的返回值,
例如 ,
於是調用方隻需要 :
mov r12d, eaxtest rcx, rcx ; Continuation 是否為 nulljne SUSPEND ; 如果不為 null,無論暫停還是不暫停 ,說明被調用的 Fib沒有同步完成 。直到整個異步調用鏈完成。Green Thread 需要運行時在用戶態實現線程調度,所有的異步抽象開銷全部消失了!這意味著整個調用鏈中沒有創建任何 Task對象,但 C++ 並不要求 async 關鍵字
。這通常意味著每次調用異步方法都會創建一個新的 Task對象 。輪到 JIT 編譯器這個方法的時候總該能判斷了吧
?其實也不行
。
Runtime Async 給 .NET 運行時引入了一套全新的調用約定:Async Calling Convention
。既然 C# 編譯器無法判斷,
JIT 才會在這一刻真正創建保存當前執行狀態所需要的 Continuation