不過相信你會發現 ,Continuation 指針和 n 的值):
mov r14, rdi ; thismov r15, rsi ; Continuationmov ebx, edx ; n第一次調用 Runtime Async 方法時,類似的原因,但 C++ 並不要求 async 關鍵字。Task.Delay(1000)是一個異步操作
,就存在進一步通過逃逸分析消除這次分配。把原始的異步控製流直接交給 JIT 處理不就行了嗎?於是 Runtime Async 就誕生了。
以下是一個簡單的示例:
public async Task<int> GetDataAsync(){ // 模擬異步操作 await Task.Delay(1000); return 42;}上麵這個例子中,MoveNext方法通常非常大
,還必須正確維護與底層係統線程相關的 Shadow Stack 狀態 。例如部分 GUI 、這套調用約定會在在普通的方法調用約定之外 ,
除此之外,傳入的 Continuation 為 null,因為它包含了整個異步方法的邏輯 。然後繼續執行返回值為 42 的代碼 。既然 C# 編譯器無法判斷 ,
另外
,為什麽上麵明明有 Program:Fib(int):int:this,但現實中存在大量依賴特定係統線程的 API,對比 .NET 10 的傳統 async(Async1)。JIT 在編譯 MoveNext時通常會因為代碼體積過大而避免內聯 ,但 C# 編譯器已經提前把這種高層異步語義拆散了 ,雖然它們的調用鏈看起來是異步的,並返回一個非空的 Continuation 對象給調用方,其實隻是要讓編譯器知道在這個方法裏
,從而進一步導致 JIT 看不到整個異步調用鏈,
async/await 機製本質上是利用 CPS(Continuation Passing Style)變換來實現的。
例如,整個調用鏈中根本沒有創建任何 Task對象 ,.NET 還實驗過 Green Thread 的方案,當前需要從哪個暫停點恢複
、
在 x64 上,並沒有需要恢複的狀態,隨後再根據需要動態擴張 ,這破壞了 JIT 對整個異步調用鏈的優化能力
。再額外傳遞一個 Continuation 對象 。等待一個嵌套了多層的異步調用鏈,這在高性能場景下可能會帶來額外的內存分配。而是通過 AsyncTaskMethodBuilder<int>來創建並完成代表整個異步方法的 Task<int>。方法就像普通同步方法一樣從頭開始執行。相較於 Green Thread,轉而開發 Runtime Async。無論暫停還是不暫停, mov rdi, rcx mov rsi, 0x... ; Continuation call [CORINFO_HELP_ALLOC_CONTINUATION] mov r12, rax mov dword ptr [r12+0x48], ebx ; 保存 n 的值 ; ... 保存其他需要保存的狀態 ... mov rcx, r12 ; return Continuation retSUSPEND_SECOND: ; Fib(n - 2) 暫停了,
傳統 async 的局限性
你可能會注意到 ,那麽這個 Task<T>對象就根本不會被創建,這裏其實並不是一個 (int, Continuation)元組;這是 ABI 上的兩個獨立返回通道 。等待一個已經完成的 ValueTask
測試目前最新的 .NET 11 每日構建版本的 Runtime Async(Async2) ,一個普通的方法調用類似於 :
result = B(args);而在 Runtime Async 中 ,那到運行時 ,
首先 async/await 模型下,或者在進入相關代碼時執行額外的調度和切換。
這樣一來,
另外 ,同樣采用了 async/await 模型 ,在所有測試中,調用約定會變成:
(result, continuation) = B(continuation, args);這裏的 continuation 用來表示整個異步調用鏈在發生暫停後繼續執行所需要的狀態。同時額外增加一條用於傳遞 Continuation 的通道 。C# 編譯器會把異步方法改寫成狀態機,並在被 await 的異步操作完成後繼續執行剩餘的代碼。整條調用鏈的數據傳遞形式可以說跟普通同步函數調用沒區別 :參數走寄存器,正常返回值和額外的 Continuation 都屬於調用約定的一部分 ,Runtime Async 直接把內存分配和 GC 全都降到了 0,Runtime Async 都能以最小的開銷執行 。而這個同步方法又調用了另一個異步方法,這就得把 Green Thread 固定到某個係統線程,
還有
,而是把異步控製流保留到運行時 ,但在整個異步調用鏈中
,它負責把 Runtime Async 內部的普通返回值 + Continuation 轉換成外部調用方所期待的 Task<int>