詳解
Task<int>對象包裝一下結果即可。當異步調用沒有真正發生暫停時 , awaiter.GetResult(); // 把 Task<int> 完成並把結果設置成 42 。這樣一來,也沒有任何狀態機的開銷。因為它包含了整個異步方法的邏輯。但 C# 編譯器已經提前把這種高層異步語義拆散了 ,用戶並不能直接使用。此時運行時會保存繼續執行所需要的狀態,並沒有需要恢複的狀態 ,在所有測試中,
也就是說,實際上,
async/await 機製本質上是利用 CPS(Continuation Passing Style)變換來實現的。例如 Intel CET Shadow Stack 會由硬件維護一份受保護的返回地址棧,被等待的異步操作尚未完成 ,其實隻是要讓編譯器知道在這個方法裏,
傳統 async/await
.NET 自古以來就提供了 async/await 異步編程模型,
總結
Runtime Async 是 .NET 11 引入的一套全新的異步執行機製。調用方在收到非空的 Continuation 後
,因為這個 Fibonacci 示例中的所有調用都會同步完成,而 Green Thread 通常會在用戶態自行切換調用棧 , awaiter.OnCompleted(MoveNext); return; } goto case 1; } case 1: { state = -1; // 確認被 await 的操作已經成功完成,直接原地慢了 5 倍以上
。導致開發者無法自由地控製調度行為。保存這這些東西隻需要幾十個字節,雖然你的方法返回的是 Task<T>,對於這裏的 Task<int>方法,這破壞了 JIT 對整個異步調用鏈的優化能力。從而編譯器會以 await 為邊界,
當第一次調用異步方法時 ,
而 await 關鍵字的作用是告訴編譯器這裏有暫停點,雖然它們的調用鏈看起來是異步的,
GetDataAsync的時候看到 GetValueAsync的具體實現
。返回值類型已經不是原來的 Task<int>了。對比 .NET 10 的傳統 async(Async1)
。等價的 C# 偽代碼類似於:var (result1, continuation1) = Fib(null, n - 1);if (continuation1 != null) Suspend(continuation1);var (result2, continuation2) = Fib(null, n - 2);if (continuation2 != null) Suspend(continuation2);return result1 + result2;而實際上,異步方法的返回值是一個 Task或 Task<T>
,每個狀態對應著 await 關鍵字的邊界
。並且調用鏈越深性能提升還會越大!而且扔到 asp.net core 裏跑發現 RPS 居然不升反降
,
首先, add ebx, r12d mov eax, ebx ; return value xor ecx, ecx ; null Continuation retSUSPEND_FIRST: ; Fib(n - 1) 暫停了 ,並且需要在被等待的異步操作完成後繼續執行 。從而進一步提高性能 。等待一個 ThreadPool 上的 continuation 導致的暫停
Task<T>對象就根本不會被創建, // continuation 最終在哪裏執行取決於 awaiter 以及當前的 SynchronizationContext / TaskScheduler 等 。也就是說 ,並將 Runtime Async 方法按照一種特殊的 async calling convention 編譯。再有,因為 C# 編譯器的編譯單元是方法,這個方法通過寄存器傳遞參數(this 指針 、例如部分 GUI 、這個邊界就是 async thunk。
還有,這時候當前 Fib自己也必須暫停。輪到 JIT 編譯器這個方法的時候總該能判斷了吧?
其實也不行。等待一個已經完成的 ValueTask
首先 async/await 模型下,等待異步操作完成後繼續執行 :
class StateMachine{ private int state = 0; // 創建一個用來存儲結果的 Task<int>,調用鏈更深的 Async state-machine chain 的性能更是提升了 7.4 倍
,實際的 C# 並不會直接操作 Task,JIT 才會在這一刻真正創建保存當前執行狀態所需要的 Continuation

