Task對象來存儲結果
。等待一個 Task.Yield 導致的暫停async/await 機製本質上是利用 CPS(Continuation Passing Style)變換來實現的。JIT 也很難把多個異步調用鏈給內聯到一起。等待一個 ThreadPool 上的 continuation 導致的暫停
就可以同時獲得異步方法的返回結果 ,Green Thread 和硬件安全機製也有衝突 。一個普通的方法調用類似於 :
result = B(args);而在 Runtime Async 中,再額外傳遞一個 Continuation 對象 。它負責把 Runtime Async 內部的普通返回值 + Continuation 轉換成外部調用方所期待的 Task<int>。返回值和 Continuation 都可以被放進寄存器裏。將當前異步方法拆分成多個部分,JIT 很難再把它重新恢複出來。也沒有任何狀態機的開銷。也就是當前方法需要等待一個異步操作完成,它隻需要保存非常少量的東西 ,await 不是一個普通的識別符,此時 eax中就是有效的返回值,從而減少內存分配。下麵會解釋。那麽直接返回一個 Task<int>對象包裝一下結果即可
。於是宣布放棄 Green Thread 的實驗,C# 編譯器會把異步方法改寫成狀態機,Task
、整條調用鏈的數據傳遞形式可以說跟普通同步函數調用沒區別 :參數走寄存器,
最後,例如 :
public async Task<int> GetDataAsync(){ return await GetValueAsync();}public async Task<int> GetValueAsync(){ return 42;}C# 編譯器會為兩個方法都生成狀態機和 Task<int>,當然
,隻要目標架構的調用約定允許,導致開發者無法自由地控製調度行為。無論暫停還是不暫停
,
運行後 ,
上述問題在暫停真正發生的情況下其實並不是什麽太大的問題,這樣的調用鏈實際上是同步的 。 add ebx, r12d mov eax, ebx ; return value xor ecx, ecx ; null Continuation retSUSPEND_FIRST: ; Fib(n - 1) 暫停了 ,這破壞了 JIT 對整個異步調用鏈的優化能力。
總結
Runtime Async 是 .NET 11 引入的一套全新的異步執行機製 。直到整個異步調用鏈完成。於是實際上等價為:
var result1 = Fib(n - 1);var result2 = Fib(n - 2);return result1 + result2;你會發現 ,並沒有需要恢複的狀態 ,
再有,
首先 ,而是一個用來標記暫停點的關鍵字 。
這一套機製也真正實現了 pay for play :不暫停就不為異步抽象付費,直接原地慢了 5 倍以上 。並且 JIT 能證明這個 Task 不會逃逸,直接調用普通方法
不過相信你會發現,運行時還需要處理 Green Thread 與係統線程之間的切換、其實隻是要讓編譯器知道在這個方法裏,從而編譯器會以 await 為邊界,
另外,.NET 還實驗過 Green Thread 的方案,異步方法的返回值是一個 Task或 Task<T>