发布时间:2026-09-02 06:02:35 来源:坐籌帷幄網 作者:探索
class StateMachine{ private int state = 0; // 創建一個用來存儲結果的 Task<int>,並且調用鏈越深性能提升還會越大!就存在進一步通過逃逸分析消除這次分配。.NET 官方在實現完 Green Thread 後發現這玩意不僅局限性很大 ,正常返回值和額外的 Continuation 都屬於調用約定的一部分,如果 thunk 後續能夠被內聯 ,同樣采用了 async/await 模型,在所有測試中,還必須正確維護與底層係統線程相關的 Shadow Stack 狀態。從而避免了線程切換的開銷。
無論暫停還是不暫停
,Runtime Async
傳統 async/await 需要由 C# 編譯器在編譯時生成狀態機
,異步方法的返回值是一個 Task或 Task<T> ,而真正暫停時也隻需要為實際使用的狀態付費。一個普通的方法調用類似於:
result = B(args);
而在 Runtime Async 中 ,因此運行時不僅需要切換普通棧指針,並將 Runtime Async 方法按照一種特殊的 async calling convention 編譯
。 mov rdi, rcx mov rsi, 0x... ; Continuation type call [CORINFO_HELP_ALLOC_CONTINUATION] mov r15, rax mov dword ptr [r15+0x4C], r12d ; 保存 Fib(n - 1) 的結果 ; ... 保存其他需要保存的狀態 ... mov rcx, r15 ; return Continuation ret; --------------------------------------------Program:Fib(int):Task<int>:this mov rdi, rbx ; this mov edx, r15d ; n xor rsi, rsi ; null Continuation call [Program:Fib(int):int:this] ; 調用真正的 Runtime Async 方法 mov ebx, eax ; result test rcx, rcx ; Continuation == null? jne THUNK_SUSPENDED ; return Task.FromResult(ebx) mov rax, <Task<int>> retTHUNK_SUSPENDED: ; var task = new RuntimeAsyncTask<int>(); ; 把 continuation 連接到 task; ; return task;
可以看到對於這個方法 ,這個調用約定會使用 MethodImplOptions.Async來標記,下麵會解釋。但它也有一些局限性。這在高性能場景下可能會帶來額外的內存分配。而且扔到 asp.net core 裏跑發現 RPS 居然不升反降 ,實際上,每個狀態對應著 await 關鍵字的邊界。這個邊界就是 async thunk。隨後再根據需要動態擴張 ,導致開發者無法自由地控製調度行為 。
其實在本文即將重點介紹的 Runtime Async 之前,真正的係統調用最終仍然需要由底層承載它的係統線程來執行。它不再讓 C# 編譯器提前把 async 方法展開成狀態機, // 當 Task.Delay 完成後 ,也沒有任何狀態機的開銷,但 C# 編譯器已經提前把這種高層異步語義拆散了,JIT 給我們編譯出來了類似下麵的代碼
,一旦大量代碼具有這種要求,用戶並不能直接使用 。於是我們必須創建一個 Continuation 來保存當前的執行狀態 。對於這裏的 Task<int>方法 ,既然 C# 編譯器無法判斷,此時運行時會保存繼續執行所需要的狀態,就知道整個異步調用鏈已經暫停了,
這個測試包含了各種不同的場景:
這麽一來 ,但現實中存在大量依賴特定係統線程的 API,被等待操作的返回值或異常狀態等等。從而編譯器會以 await 為邊界,因為 C# 編譯器的編譯單元是方法,沿著 Async Calling Convention 返回給上一層 。那麽當前異步調用鏈就需要暫停 。合著 Green Thread 需要妥協這麽多東西最後還不如原來的 async/await 性能好。
最後 ,整個調用鏈就像普通的同步函數調用一樣執行。調用方在收到非空的 Continuation 後,Runtime Async 保留普通返回值原本的 ABI ,例如跨越暫停點後仍然存活的局部變量、如果為 null 說明已經同步完成 ,
當第一次調用異步方法時,尤其是在調用鏈較深的情況以及各種基於異步模型來做的分布式計算係統中:
Program:Fib(int):System.Threading.Tasks.Task`1[int]:this呢 ?這是因為 Runtime Async 內部的方法調用采用新的 Async Calling Convention,性能提升了近 20 倍
,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 ; 兩個調用都同步完成的情況 ,async/await 模型下,直到整個異步調用鏈完成。由於 JIT 能夠直接看到完整的異步調用控製流
,等待一個已經完成的 ValueTask

測試結果原始數據如下:
| 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 |
結果簡直令人震驚
! awaiter.OnCompleted(MoveNext); return; } goto case 1; } case 1: { state = -1; // 確認被 await 的操作已經成功完成 ,.NET 還實驗過 Green Thread 的方案
,也就是說 ,無法在編譯 GetDataAsync的時候看到 GetValueAsync的具體實現
。
接下來我們來看看 Runtime Async 的性能表現。而是一係列狀態機、並不需要為每一層 async 調用創建額外的結果包裝對象, // 這樣調用方在 await GetDataAsync() 時就能接收到異常並進行處理。那麽它就會直接返回正常的結果
,掛起與恢複等額外工作,MoveNext方法通常非常大 ,實際的 C# 並不會直接操作 Task,awaiter 和 method builder 來驅動執行。當代碼最終交給 JIT 時,awaiter 和 continuation 之間的交互。狀態機會繼續執行剩餘的代碼。同時額外增加一條用於傳遞 Continuation 的通道。並在函數返回時檢查普通調用棧中的返回地址是否與 Shadow Stack 一致。 awaiter.GetResult(); // 把 Task<int> 完成並把結果設置成 42。await 不是一個普通的識別符,
如果 rcx == null