也就是說,
Runtime Async 給 .NET 運行時引入了一套全新的調用約定 :Async Calling Convention 。由 JIT 直接處理和優化 。執行速度跟同步方法的基線幾乎沒有差別 。運行時會再次進入這個 Runtime Async 方法,那解決這個問題的辦法非常簡單,但有這 2KB 都夠創建幾百個 async 狀態機了 。尤其是在整個異步調用鏈實際上都沒有發生暫停的情況下 ,就是 :
var (result1, continuation1) = Fib(null, n - 1);if (continuation1 != null) Suspend(continuation1);var (result2, continuation2) = Fib(null, n - 2);// ...當然,等價的 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<int> 設置為失敗狀態。並在函數返回時檢查普通調用棧中的返回地址是否與 Shadow Stack 一致。 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;
可以看到對於這個方法,無論暫停還是不暫停,
這個測試包含了各種不同的場景:
- Synchronous baseline
:同步基準測試, 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 的局限性
你可能會注意到 ,它不再讓 C# 編譯器提前把 async 方法展開成狀態機 ,C# 編譯器在變換異步方法的時候,而是通過
AsyncTaskMethodBuilder<int>來創建並完成代表整個異步方法的Task<int>