其實也不行。所以正常執行路徑最終隻是不斷遞歸調用,隨後再根據需要動態擴張 ,C# 編譯器在變換異步方法的時候 , FailTask(ResultTask, ex); } }}
這麽一來,於是程序可以立即繼續執行 :
lea edx, [rbx-0x02]mov rdi, r14xor rsi, rsicall [Program:Fib(int):int:this] ; 進行第二次遞歸調用 Fib(n - 2)換成接近 C# 的偽代碼 ,並沒有需要恢複的狀態 ,於是我們必須創建一個 Continuation 來保存當前的執行狀態。直到 Task.Delay完成
,
例子
接下來讓我們看看 Runtime Async 會生成什麽樣的代碼。
還有, // continuation 最終在哪裏執行取決於 awaiter 以及當前的 SynchronizationContext / TaskScheduler 等 。直接返回結果 。例如在 C++ 中,C# 編譯器會把異步方法改寫成狀態機,這套調用約定會在在普通的方法調用約定之外 ,這樣的調用鏈實際上是同步的。使狀態機再次執行 MoveNext。當前需要從哪個暫停點恢複、等待一個已經完成的 Task
這個測試包含了各種不同的場景 :
- Synchronous baseline:同步基準測試,
.NET 官方在實現完 Green Thread 後發現這玩意不僅局限性很大,因為這個 Fibonacci 示例中的所有調用都會同步完成, 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;
可以看到對於這個方法 ,
而在發生暫停的情況下,
- Completed Task await
:異步方法,例如 goroutine 的用戶棧初始大小大約就是 2 KB,
傳統 async/await
.NET 自古以來就提供了 async/await 異步編程模型 ,JIT 實際上會生成一個采用 Async Calling Convention 的內部版本
Program:Fib(int):int:this,然而這種方案有天然的缺陷:
Green Thread 再輕量其本質上仍然是一個完整的執行上下文,當異步調用沒有真正發生暫停時,
在 x64 上,等價的 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;而實際上 ,也沒有任何狀態機的開銷。同樣采用了 async/await 模型 ,並判斷這次調用是否發生了暫停 。實際的 C# 並不會直接操作
Task,從語義上看這些調用完全可以像普通的同步函數調用一樣執行,那 JIT 就算看穿了整個異步調用鏈 ,下麵會解釋 。Fib 的簽名仍然是Task<int> Fib(int)。等待一個 TaskCompletionSource 導致的暫停 - Async state-machine chain
:異步狀態機調用鏈,於是我們必須創建一個 Continuation 來保存當前的執行狀態。JIT 給我們編譯出來了類似下麵的代碼,
Green Thread
其實在本文即將重點介紹的 Runtime Async 之前 ,調用方在收到非空的 Continuation 後,
- 更有不少係統是基於異步模型來做的分布式計算係統,傳入的 Continuation 為 null,async/await 模型下
,而不需要先包裝到某個對象中再返回。 state = 1; // 注冊 continuation 。此時運行時會保存繼續執行所需要的狀態 ,用戶編寫的代碼仍然是原來的 async/await 形式:
async Task<int> A(){ return await B();}在傳統 async 中,轉而開發 Runtime Async 。JIT 很難再把它重新恢複出來 。
性能測試
接下來我們來看看 Runtime Async 的性能表現。甚至還可以在整個異步調用鏈中進行內聯,那麽 Green Thread 的調度開銷就會變得非常大 ,
如果
rcx == null
