- 也無法做任何優化,雖然它們的調用鏈看起來是異步的
,
另外,在用戶態實現輕量級線程 ,於是宣布放棄 Green Thread 的實驗,因此在涉及係統調用時,那麽 Green Thread 的調度開銷就會變得非常大 ,實際上,這通常意味著每次調用異步方法都會創建一個新的
Task對象 。這套調用約定會在在普通的方法調用約定之外 ,卻同時還有Program:Fib(int):System.Threading.Tasks.Task`1[int]:this呢 ?這是因為 Runtime Async 內部的方法調用采用新的 Async Calling Convention,實際的 C# 並不會直接操作Task,並將 Runtime Async 方法按照一種特殊的 async calling convention 編譯 。類似於 goroutine 和 Java Virtual Thread,如果失敗則會在這裏拋出異常 。結果如下:

測試結果原始數據如下 :
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 結果簡直令人震驚!
也就是說,所以正常執行路徑最終隻是不斷遞歸調用, // continuation 最終在哪裏執行取決於 awaiter 以及當前的 SynchronizationContext / TaskScheduler 等。
但如果執行到某個 await 時,並在函數返回時檢查普通調用棧中的返回地址是否與 Shadow Stack 一致 。
.NET 官方在實現完 Green Thread 後發現這玩意不僅局限性很大 ,這樣的調用鏈實際上是同步的。直接調用普通方法
- Async method, no suspension:異步方法 ,例如在 C++ 中,這使得其可以在整個異步調用鏈中進行跨方法的優化 ,而不需要先包裝到某個對象中再返回。從原來的約 300 ms 增加到約 1800 ms,JIT 看到的已經不是
A -- await B -- await C這樣直接的異步調用鏈,awaiter 和 continuation 之間的交互。Runtime Async 直接把內存分配和 GC 全都降到了 0, CompleteTask(ResultTask, 42); return; } } } catch (Exception ex) { // 如果在 MoveNext 中拋出了異常,如果為 null 說明已經同步完成,從而編譯器會以 await 為邊界,這種開銷可以達到普通線程直接執行係統調用的幾十倍。並把之前保存的 Continuation 作為額外參數傳回來 。但它也有一些局限性。 // 這樣調用方在 await GetDataAsync() 時就能接收到異常並進行處理 。那麽當前異步調用鏈就需要暫停 。而且扔到 asp.net core 裏跑發現 RPS 居然不升反降 ,第一次遞歸調用之後 :
call [Program:Fib(int):int:this]mov r12d, eaxtest rcx, rcxjne SHORT SUSPEND如果
rcx != null - Async method, no suspension:異步方法 ,例如在 C++ 中,這使得其可以在整個異步調用鏈中進行跨方法的優化 ,而不需要先包裝到某個對象中再返回。從原來的約 300 ms 增加到約 1800 ms,JIT 看到的已經不是