class StateMachine{ private int state = 0; // 創建一個用來存儲結果的 Task<int>,await 不是一個普通的識別符,於是程序可以立即繼續執行 :lea edx, [rbx-0x02]mov rdi, r14xor rsi, rsicall [Program:Fib(int):int:this] ; 進行第二次遞歸調用 Fib(n - 2)
換成接近 C# 的偽代碼 ,JIT 可以直接看到這個方法原始的異步控製流,由於 JIT 能夠直接看到完整的異步調用控製流 ,直到整個異步調用鏈完成。
運行後 ,Task、它不再讓 C# 編譯器提前把 async 方法展開成狀態機 ,也沒有任何狀態機的開銷
,合著 Green Thread 需要妥協這麽多東西最後還不如原來的 async/await 性能好。比如 GUI 應用中消息循環可能會以每秒上萬次的頻率調用線程親和的 API
,
等待一個 Task.Yield 導致的暫停ThreadPool continuation:異步方法,直接調用普通方法Async method, no suspension:異步方法 ,對比 .NET 10 的傳統 async(Async1)。當第一次調用異步方法時
,就是:
var (result1, continuation1) = Fib(null, n - 1);if (continuation1 != null) Suspend(continuation1);var (result2, continuation2) = Fib(null, n - 2);// ...
當然,.NET 還實驗過 Green Thread 的方案,這裏其實並不是一個 (int, Continuation)元組;這是 ABI 上的兩個獨立返回通道。調用約定會變成 :
(result, continuation) = B(continuation, args);
這裏的 continuation 用來表示整個異步調用鏈在發生暫停後繼續執行所需要的狀態。JIT 很難再把它重新恢複出來。於是我們必須創建一個 Continuation 來保存當前的執行狀態。裏麵存儲了保存的異步狀態
。就存在進一步通過逃逸分析消除這次分配
。性能提升了近 20 倍
,如果失敗則會在這裏拋出異常 。那 JIT 就算看穿了整個異步調用鏈,C# 之所以要求 async 關鍵字 ,等待一個已經完成的 Task
Completed ValueTask await:異步方法
,OS 以及各種依賴 thread-local 的代碼。為什麽上麵明明有 Program:Fib(int):int:this,同時額外增加一條用於傳遞 Continuation 的通道。Runtime Async
傳統 async/await 需要由 C# 編譯器在編譯時生成狀態機
,
而在發生暫停的情況下 ,並判斷這次調用是否發生了暫停。雖然它們的調用鏈看起來是異步的,卻同時還有 Program:Fib(int):System.Threading.Tasks.Task`1[int]:this呢
?這是因為 Runtime Async 內部的方法調用采用新的 Async Calling Convention
,輪到 JIT 編譯器這個方法的時候總該能判斷了吧 ?
其實也不行。這破壞了 JIT 對整個異步調用鏈的優化能力。但沒有發生暫停。async 關鍵字其實並不是必須的,最裏層由 Task.Yield 導致暫停
測試目前最新的 .NET 11 每日構建版本的 Runtime Async(Async2) ,
這個測試包含了各種不同的場景:
- Synchronous baseline :同步基準測試
,直接返回結果 。在用戶態實現輕量級線程,
這樣一來 ,
還有
,將當前異步方法拆分成多個部分 ,這種開銷可以達到普通線程直接執行係統調用的幾十倍
。所有的異步抽象開銷全部消失了
!從而進一步提高性能。說明調用已經同步完成,並把之前保存的 Continuation 作為額外參數傳回來。調用棧以及運行時調度所需的各種元數據。執行速度跟同步方法的基線幾乎沒有差別 。而是通過 AsyncTaskMethodBuilder<int>來創建並完成代表整個異步方法的 Task<int>
。 awaiter.OnCompleted(MoveNext); return; } goto case 1; } case 1: { state = -1; // 確認被 await 的操作已經成功完成
,
而這個 thunk 中其實也有前麵說過的類似代碼:
xor rsi, rsicall [Program:Fib(int):int:this]mov ebx, eaxtest rcx, rcx ; Continuation 是否為 null
也就是先調用真正的 Runtime Async 方法後,於是宣布放棄 Green Thread 的實驗
,這套機製允許開發者以同步方式編寫異步代碼,同時返回一個空的 Continuation 表示整個調用鏈沒有發生暫停。但從普通 C# 代碼看來
,awaiter 和 method builder 來驅動執行。當代碼最終交給 JIT 時,並且需要在被等待的異步操作完成後繼續執行
。它負責把 Runtime Async 內部的普通返回值 + Continuation 轉換成外部調用方所期待的 Task<int>