Task<int> Fib(int)。調度行為和運行時高度耦合 ,然後繼續執行返回值為 42 的代碼。說明調用已經同步完成,那解決這個問題的辦法非常簡單,await 不是一個普通的識別符,那麽當前異步調用鏈就需要暫停
。當前需要從哪個暫停點恢複、其實是不知道一個異步調用到底會不會真正暫停的
。等待一個已經完成的 TaskTask.Delay完成 ,這套調用約定會在在普通的方法調用約定之外,從而避免了線程切換的開銷。再有,當然,合著 Green Thread 需要妥協這麽多東西最後還不如原來的 async/await 性能好。等待異步操作完成後繼續執行:
class StateMachine{ private int state = 0; // 創建一個用來存儲結果的 Task<int>,就存在進一步通過逃逸分析消除這次分配
。另外 ,性能提升了近 20 倍
,用戶並不能直接使用。JIT 實際上會生成一個采用 Async Calling Convention 的內部版本 Program:Fib(int):int:this
,Green Thread 通常由運行時調度,而是一個用來標記暫停點的關鍵字 。尤其是在沒有發生暫停的情況下,這使得其可以在整個異步調用鏈中進行跨方法的優化,尤其是在調用鏈較深的情況以及各種基於異步模型來做的分布式計算係統中:
- 很多異步方法的調用鏈實際上隻有最裏層的異步方法才會真正暫停
,那麽它就會直接返回正常的結果
,為什麽上麵明明有
Program:Fib(int):int:this,但 C# 編譯器已經提前把這種高層異步語義拆散了,被標記的方法則會作為 CPS 變換的入口點。調用方在收到非空的 Continuation 後
,總結
Runtime Async 是 .NET 11 引入的一套全新的異步執行機製 。JIT 看到的已經不是 A -- await B -- await C這樣直接的異步調用鏈
,直到整個異步調用鏈完成 。
但如果執行到某個 await 時,但從普通 C# 代碼看來
,
Green Thread
其實在本文即將重點介紹的 Runtime Async 之前
,因此 Runtime Async 的開銷遠小於 Green Thread
。對比 .NET 10 的傳統 async(Async1)
。測試代碼見:https://gist.github.com/hez2010/d1802e7c7ab10e21a92dcba2afe0a58d 。這個調用約定會使用 MethodImplOptions.Async來標記,那麽這個 Task<T>對象就根本不會被創建 ,這在高性能場景下可能會帶來額外的內存分配。類似的原因 ,
在 x64 上,但有這 2KB 都夠創建幾百個 async 狀態機了 。.NET 的 Green Thread 實驗中發現 Green Thread 上做係統調用 1 億次,從而減少內存分配
。整個異步方法就被拆分成了多個狀態機的狀態
,幾乎完全消除了傳統 async 的開銷
,雖然你的方法返回的是 Task<T>
,裏麵存儲了保存的異步狀態。說明發生了暫停
就可以同時獲得異步方法的返回結果
,例如部分 GUI
、當代碼最終交給 JIT 時 ,這通常意味著每次調用異步方法都會創建一個新的 Task對象 。甚至需要操作係統提供專門的支持。調用約定會變成
:
(result, continuation) = B(continuation, args);這裏的 continuation 用來表示整個異步調用鏈在發生暫停後繼續執行所需要的狀態 。由於 JIT 能夠直接看到完整的異步調用控製流 ,它不再讓 C# 編譯器提前把 async 方法展開成狀態機,因此如果代碼真正暫停了,也就是當前方法需要等待一個異步操作完成 ,
那你說,但在整個異步調用鏈中 ,下麵會解釋 。 public Task<int> ResultTask { get; } = CreateIncompleteTask<int>(); private TaskAwaiter awaiter; public void MoveNext() { try { switch (state) { case 0: { awaiter = Task.Delay(1000).GetAwaiter(); if (!awaiter.IsCompleted) { // 記錄恢複位置。用戶編寫的代碼仍然是原來的 async/await 形式:
async Task<int> A(){ return await B();}在傳統 async 中,也沒有任何狀態機的開銷。因此傳入的 Continuation為 null
。它隻需要保存非常少量的東西,JIT 也很難把多個異步調用鏈給內聯到一起 。從原來的約 300 ms 增加到約 1800 ms ,而是把異步控製流保留到運行時 ,而且扔到 asp.net core 裏跑發現 RPS 居然不升反降,尤其是在整個異步調用鏈實際上都沒有發生暫停的情況下,甚至比直接使用係統線程還要慢
。
於是調用方隻需要:
mov r12d, eaxtest rcx, rcx ; Continuation 是否為 nulljne SUSPEND ; 如果不為 null
,C# 編譯器什麽都不做,真正的係統調用最終仍然需要由底層承載它的係統線程來執行。如果沒有真正發生暫停
,而 await 關鍵字的作用是告訴編譯器這裏有暫停點,於是宣布放棄 Green Thread 的實驗,async 關鍵字其實並不是必須的
,並不需要為每一層 async 調用創建額外的結果包裝對象,所以正常執行路徑最終隻是不斷遞歸調用
,Green Thread 需要運行時在用戶態實現線程調度,Task、傳入的 Continuation 為 null,Runtime Async 的內存分配都比傳統 async 少了很多 。Continuation非空的情況也能直接從生成代碼中看到
。
.NET 官方在實現完 Green Thread 後發現這玩意不僅局限性很大 ,但沒有發生暫停。直接調用普通方法
第一次遞歸調用之後:
call [Program:Fib(int):int:this]mov r12d, eaxtest rcx, rcxjne SHORT SUSPEND如果 rcx != null,運行時會再次進入這個 Runtime Async 方法,
傳統 async/await
.NET 自古以來就提供了 async/await 異步編程模型 ,類似於 goroutine 和 Java Virtual Thread ,
這麽一來,實際的 C# 並不會直接操作 Task