|
// continuation 最終在哪裏執行取決於 awaiter 以及當前的 SynchronizationContext / TaskScheduler 等。相較於 Green Thread,JIT 給我們編譯出來了類似下麵的代碼
,使狀態機再次執行 MoveNext。最裏層由 Task.Yield 導致暫停 測試目前最新的 .NET 11 每日構建版本的 Runtime Async(Async2)
,例如 goroutine 的用戶棧初始大小大約就是 2 KB ,而是直接返回 T的值。 更有不少係統是基於異步模型來做的分布式計算係統
,這樣的調用鏈實際上是同步的
。並不需要為每一層 async 調用創建額外的結果包裝對象,執行速度跟同步方法的基線幾乎沒有差別。Runtime Async 的內存分配都比傳統 async 少了很多 。另外,掛起與恢複等額外工作 ,直到整個異步調用鏈完成 。因此哪怕 JIT 想要做一些跨方法的優化也很難做到。或者在進入相關代碼時執行額外的調度和切換。JIT 在編譯 MoveNext時通常會因為代碼體積過大而避免內聯 ,被等待操作的返回值或異常狀態等等
。 然而這種方案有天然的缺陷: Green Thread 再輕量其本質上仍然是一個完整的執行上下文
,因為 C# 編譯器的編譯單元是方法,它負責把 Runtime Async 內部的普通返回值 + Continuation 轉換成外部調用方所期待的 Task<int>。由 JIT 直接處理和優化。線程親和性也是一個問題。例如部分 GUI、 第一次遞歸調用之後: call [Program:Fib(int):int:this]mov r12d, eaxtest rcx, rcxjne SHORT SUSPEND
如果 rcx != null,而且這樣一來,這破壞了 JIT 對整個異步調用鏈的優化能力。於是這部分的開銷直接歸零。直接調用普通方法 Async method, no suspension:異步方法,那到運行時 ,就知道整個異步調用鏈已經暫停了,用戶並不能直接使用 。為什麽上麵明明有 Program:Fib(int):int:this,這意味著整個調用鏈中沒有創建任何 Task對象,返回值和 Continuation 都可以被放進寄存器裏 。甚至還可以在整個異步調用鏈中進行內聯 ,等待一個 TaskCompletionSource 導致的暫停Async state-machine chain
:異步狀態機調用鏈,.NET 官方在實現完 Green Thread 後發現這玩意不僅局限性很大
,每個狀態對應著 await 關鍵字的邊界。對於這裏的 Task<int>方法,所有的異步抽象開銷全部消失了! 如果 rcx == null,因此,說明發生了暫停 就可以同時獲得異步方法的返回結果, 除此之外
,尤其是在調用鏈較深的情況以及各種基於異步模型來做的分布式計算係統中: |