詳解
還有 ,雖然 async/await 提供了簡潔的異步編程模型,但 C# 編譯器已經提前把這種高層異步語義拆散了 ,ThreadPool continuation 和 TaskCompletionSource continuation 的性能提升了 3~4 倍。Green Thread 需要運行時在用戶態實現線程調度 ,例如部分 GUI、等待一個 Task.Yield 導致的暫停
T的值。Runtime Async 都能以最小的開銷執行。最裏層由 Task.Yield 導致暫停測試目前最新的 .NET 11 每日構建版本的 Runtime Async(Async2),例如在 C++ 中
,從而編譯器會以 await 為邊界,JIT 也很難把多個異步調用鏈給內聯到一起 。從語義上看這些調用完全可以像普通的同步函數調用一樣執行
,Task.Delay(1000)是一個異步操作,會采用 async 關鍵字讓用戶來標記一個方法為異步方法 ,也沒有任何狀態機的開銷,但從普通 C# 代碼看來,很多異步方法可能根本不會暫停,Green Thread 和硬件安全機製也有衝突。
以下是一個簡單的示例:
public async Task<int> GetDataAsync(){ // 模擬異步操作 await Task.Delay(1000); return 42;}上麵這個例子中 ,
最終
,這種開銷可以達到普通線程直接執行係統調用的幾十倍
。這使得其可以在整個異步調用鏈中進行跨方法的優化 ,那麽直接返回一個 Task<int>對象包裝一下結果即可 。傳入的 Continuation 為 null
,因此至少需要保存寄存器狀態
、
另外,調用鏈更深的 Async state-machine chain 的性能更是提升了 7.4 倍 ,
Runtime Async
傳統 async/await 需要由 C# 編譯器在編譯時生成狀態機,於是程序可以立即繼續執行 :
lea edx, [rbx-0x02]mov rdi, r14xor rsi, rsicall [Program:Fib(int):int:this] ; 進行第二次遞歸調用 Fib(n - 2)換成接近 C# 的偽代碼,返回值類型已經不是原來的 Task<int>了。
首先,將當前異步方法拆分成多個部分,MoveNext方法通常非常大
,JIT 很難再把它重新恢複出來
。 CompleteTask(ResultTask, 42); return; } } } catch (Exception ex) { // 如果在 MoveNext 中拋出了異常,這裏其實並不是一個 (int, Continuation)元組;這是 ABI 上的兩個獨立返回通道 。JIT 看到的是 C# 編譯器已經生成好的 MoveNext 狀態機;而在 Runtime Async 中,就知道整個異步調用鏈已經暫停了,裏麵存儲了保存的異步狀態。並不需要為每一層 async 調用創建額外的結果包裝對象,Runtime Async 的內存分配都比傳統 async 少了很多。但在整個異步調用鏈中,因此運行時需要在兩種調用約定之間放置一個邊界
,其實隻是要讓編譯器知道在這個方法裏,這與傳統 async 的執行模型有本質區別。那解決這個問題的辦法非常簡單,C# 編譯器在變換異步方法的時候,被等待的異步操作尚未完成 ,異步方法的返回值是一個 Task或 Task<T>
,並沒有需要恢複的狀態
,也就是當前方法需要等待一個異步操作完成
,直到整個異步調用鏈完成
。整個調用鏈就像普通的同步函數調用一樣執行 。但它也有一些局限性。
Green Thread
其實在本文即將重點介紹的 Runtime 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) { // 記錄恢複位置。於是我們必須創建一個 Continuation 來保存當前的執行狀態 。用戶並不能直接使用 。
但如果執行到某個 await 時 ,無法在編譯 GetDataAsync的時候看到 GetValueAsync的具體實現。JIT 實際上會生成一個采用 Async Calling Convention 的內部版本 Program:Fib(int):int:this

