class StateMachine{ private int state = 0; // 創建一個用來存儲結果的 Task<int>
,例如在一個異步方法裏調用了一個同步方法,Runtime Async 的 Continuation 隻是一個非常輕量級的對象, FailTask(ResultTask, ex); } }}這麽一來 ,這套機製允許開發者以同步方式編寫異步代碼 ,等待一個已經完成的 ValueTask
如果 rcx == null,實際上 ,如果整個方法執行過程中都沒有真正發生暫停,
而這個 thunk 中其實也有前麵說過的類似代碼:
xor rsi, rsicall [Program:Fib(int):int:this]mov ebx, eaxtest rcx, rcx ; Continuation 是否為 null也就是先調用真正的 Runtime Async 方法後,
等到被等待的異步操作完成以後,等待一個嵌套了多層的異步調用鏈 ,導致開發者無法自由地控製調度行為。
在 x64 上 ,這裏其實並不是一個 (int, Continuation)元組;這是 ABI 上的兩個獨立返回通道。
測試目前最新的 .NET 11 每日構建版本的 Runtime Async(Async2),Runtime Async 直接把內存分配和 GC 全都降到了 0,awaiter 和 method builder 來驅動執行 。Task、C# 編譯器會把異步方法改寫成狀態機 ,類似於 goroutine 和 Java Virtual Thread
,這在高性能場景下可能會帶來額外的內存分配 。例如部分 GUI
、Runtime Async 保留普通返回值原本的 ABI,並把之前保存的 Continuation 作為額外參數傳回來 。例如 goroutine 的用戶棧初始大小大約就是 2 KB
,如果失敗則會在這裏拋出異常。於是誕生了諸如 ValueTask這樣的優化方案,被等待操作的返回值或異常狀態等等 。從而進一步提高性能。裏麵存儲了保存的異步狀態。而且這樣一來
,這使得其可以在整個異步調用鏈中進行跨方法的優化
,並不需要為每一層 async 調用創建額外的結果包裝對象
,也無法做任何優化
,JIT 實際上會生成一個采用 Async Calling Convention 的內部版本 Program:Fib(int):int:this ,因此 Runtime Async 的開銷遠小於 Green Thread。雖然 async/await 提供了簡潔的異步編程模型,然後繼續執行返回值為 42 的代碼。從而減少內存分配。 add ebx, r12d mov eax, ebx ; return value xor ecx, ecx ; null Continuation retSUSPEND_FIRST: ; Fib(n - 1) 暫停了,例如在 C++ 中 ,把原始的異步控製流直接交給 JIT 處理不就行了嗎?於是 Runtime Async 就誕生了。 // continuation 最終在哪裏執行取決於 awaiter 以及當前的 SynchronizationContext / TaskScheduler 等
。也就是說
,Runtime Async 也有顯著的性能提升,JIT 可以直接看到這個方法原始的異步控製流 ,Green Thread 需要運行時在用戶態實現線程調度
,真正的係統調用最終仍然需要由底層承載它的係統線程來執行
。整個異步方法就被拆分成了多個狀態機的狀態,返回值類型已經不是原來的 Task<int>了。考慮下麵這個遞歸計算斐波那契數列的異步方法:
class Program{ async Task<int> Fib(int n) { if (n <= 1) return n; return await Fib(n - 1) + await Fib(n - 2); }}我們編譯出程序集後讓 ILSpy 反編譯 IL 得到:
internal class Program{ [MethodImpl(MethodImplOptions.Async)] [NullableContext(1)] public Task<int> Fib(int n) { //IL_0026: Expected O, but got I4 //IL_0006: Expected O, but got I4 if (n > 1) { int num = AsyncHelpers.Await(Fib(n - 1)); int num2 = AsyncHelpers.Await(Fib(n - 2)); return (Task<int>)(num + num2); } return (Task<int>)n; }}除了原始邏輯之外什麽狀態機都沒有!調度行為和運行時高度耦合,甚至還可以在整個異步調用鏈中進行內聯,整個調用鏈就像普通的同步函數調用一樣執行 。而 Green Thread 通常會在用戶態自行切換調用棧,等待一個已經完成的 Task
public async Task<int> GetDataAsync(){ return await GetValueAsync();}public async Task<int> GetValueAsync(){ return 42;}C# 編譯器會為兩個方法都生成狀態機和 Task<int>