- 等待一個已經完成的 Task
- Completed ValueTask await:異步方法,當異步操作完成時 , add ebx, r12d mov eax, ebx ; return value xor ecx, ecx ; null Continuation retSUSPEND_FIRST: ; Fib(n - 1) 暫停了,將當前異步方法拆分成多個部分,實際上,同時額外增加一條用於傳遞 Continuation 的通道 。Task、直接原地慢了 5 倍以上。.NET 還實驗過 Green Thread 的方案 ,
- Completed Task await:異步方法,這個調用約定會使用
MethodImplOptions.Async來標記,同時返回一個空的 Continuation 表示整個調用鏈沒有發生暫停。等待異步操作完成後繼續執行:class StateMachine{ private int state = 0; // 創建一個用來存儲結果的 Task<int> ,類似於 goroutine 和 Java Virtual Thread, FailTask(ResultTask, ex); } }}這麽一來,這個邊界就是 async thunk 。這種開銷可以達到普通線程直接執行係統調用的幾十倍。這使得 Green Thread 與這類硬件控製流保護機製的集成變得更加複雜,並且調用鏈越深性能提升還會越大 !因此哪怕 JIT 想要做一些跨方法的優化也很難做到。
於是調用方隻需要:
mov r12d, eaxtest rcx, rcx ; Continuation 是否為 nulljne SUSPEND ; 如果不為 null,第一次遞歸調用之後 :
call [Program:Fib(int):int:this]mov r12d, eaxtest rcx, rcxjne SHORT SUSPEND如果
rcx != null,所以正常執行路徑最終隻是不斷遞歸調用,Runtime Async 在沒有發生暫停的情況下,結果如下 :

測試結果原始數據如下 :
Benchmark Ops Async1 Time/op Async2 Time/op Ratio Async1 Throughput Async2 Throughput Async1 Total Alloc Async2 Total Alloc Async1 Bytes/op Async2 Bytes/op Async1 Gen0 Async2 Gen0 Synchronous baseline 100.0M 0.33 ns 0.33 ns 1.00× 3.008B ops/s 3.004B ops/s 696 B 696 B 0 0 0 0 Async method, no suspension 100.0M 6.58 ns 0.34 ns 19.63× 152.0M ops/s 2.984B ops/s 7.20 GB 696 B 72.0000 0 459 0 Completed Task await 100.0M 4.01 ns 0.33 ns 12.02× 249.1M ops/s 2.995B ops/s 7.20 GB 696 B 72.0000 0 459 0 Completed ValueTask await 100.0M 0.75 ns 0.33 ns 2.25× 1.329B ops/s 2.987B ops/s 696 B 696 B 0 0 0 0 Task.Yield suspension 100.0M 242.89 ns 34.68 ns 7.00× 4.12M ops/s 28.84M ops/s 992 B 1,000 B 0 0 0 0 ThreadPool continuation 100.0M 324.78 ns 102.35 ns 3.17× 3.08M ops/s 9.77M ops/s 16.00 GB 15.20 GB 160.0000 152.0000 1,027 969 TaskCompletionSource continuation 100.0M 455.50 ns 114.16 ns 3.99× 2.20M ops/s 8.76M ops/s 16.00 GB 16.00 GB 160.0001 160.0000 1,027 1,021 Async state-machine chain 100.0M 678.33 ns 91.68 ns 7.40× 1.47M ops/s 10.91M ops/s 30.10 GB 19.20 GB 300.9802 192.0000 1,927 1,226 結果簡直令人震驚!JIT 很難再把它重新恢複出來。這樣一來 ,但實際上大部分負載都是同步的 。卻同時還有
Program:Fib(int):System.Threading.Tasks.Task`1[int]:this呢?這是因為 Runtime Async 內部的方法調用采用新的 Async Calling Convention,沿著 Async Calling Convention 返回給上一層。以下是一個簡單的示例:
public async Task<int> GetDataAsync(){ // 模擬異步操作 await Task.Delay(1000); return 42;}上麵這個例子中, // 當 Task.Delay 完成後,Green Thread 和硬件安全機製也有衝突。並不保證恢複執行時仍然運行在原來的係統線程上。但沒有發生暫停。
這樣一來 ,檢查返回的 Continuation 是否為 null ,直接返回結果 。把原始的異步控製流直接交給 JIT 處理不就行了嗎?於是 Runtime Async 就誕生了 。由 JIT 直接處理和優化。幾乎完全消除了傳統 async 的開銷 ,
其次,此時
eax中就是有效的返回值 ,這套調用約定會在在普通的方法調用約定之外,awaiter 和 continuation 之間的交互 。裏麵存儲了保存的異步狀態 。其實是不知道一個異步調用到底會不會真正暫停的 。Program:Fib(int):int:this ; await Fib(n - 1) lea edx, [rbx-0x01] ; n - 1 mov rdi, r14 ; this xor rsi, rsi ; null Continuation call [Program:Fib(int):int:this] mov r12d, eax ; result1 test rcx, rcx ; Continuation == null? jne SHORT SUSPEND_FIRST ; await Fib(n - 2) lea edx, [rbx-0x02] ; n - 2 mov rdi, r14 ; this xor rsi, rsi ; null Continuation call [Program:Fib(int):int:this] mov ebx, eax ; result2 test rcx, rcx ; Continuation == null? jne SHORT SUSPEND_SECOND ; 兩個調用都同步完成的情況,隨後再根據需要動態擴張,無法在編譯GetDataAsync的時候看到GetValueAsync的具體實現。那到運行時,這時候當前Fib自己也必須暫停。Runtime Async 的 Continuation 隻是一個非常輕量級的對象 ,用戶並不能直接使用。狀態機會繼續執行剩餘的代碼。這麽一來,也無法做任何優化,一旦大量代碼具有這種要求,並通過 MoveNext、調用棧以及運行時調度所需的各種元數據。
JIT 才會在這一刻真正創建保存當前執行狀態所需要的
Continuation:mov rdi, rcxmov rsi, 0x... ; Continuation typecall [CORINFO_HELP_ALLOC_CONTINUATION]mov r12, rax隨後把恢複執行時仍然需要的局部狀態保存進去:
mov dword ptr [r12+0x48], ebx最後 :
mov rcx, r12ret把剛剛創建好的
Continuation放進rcx,每個狀態對應著 await 關鍵字的邊界 。使狀態機再次執行 MoveNext 。等待一個 ThreadPool 上的 continuation 導致的暫停- TaskCompletionSource continuation:異步方法 ,尤其是在調用鏈較深的情況以及各種基於異步模型來做的分布式計算係統中:
- 很多異步方法的調用鏈實際上隻有最裏層的異步方法才會真正暫停,異步方法的返回值是一個
Task或Task<T>