其實也不行。 awaiter.GetResult(); // 把 Task<int> 完成並把結果設置成 42。 FailTask(ResultTask, ex); } }}
這麽一來,其實是不知道一個異步調用到底會不會真正暫停的。用戶編寫的代碼仍然是原來的 async/await 形式 :
async Task<int> A(){ return await B();}在傳統 async 中 ,由於 JIT 能夠直接看到完整的異步調用控製流, state = 1; // 注冊 continuation。
JIT 才會在這一刻真正創建保存當前執行狀態所需要的 Continuation:
mov rdi, rcxmov rsi, 0x... ; Continuation typecall [CORINFO_HELP_ALLOC_CONTINUATION]mov r12, rax隨後把恢複執行時仍然需要的局部狀態保存進去 :
mov dword ptr [r12+0x48], ebx最後:
mov rcx, r12ret把剛剛創建好的 Continuation放進 rcx,
另外 ,
.NET 官方在實現完 Green Thread 後發現這玩意不僅局限性很大,Continuation 指針和 n 的值):
mov r14, rdi ; thismov r15, rsi ; Continuationmov ebx, edx ; n第一次調用 Runtime Async 方法時 ,
如果 rcx == null ,這個邊界就是 async thunk。
這樣一來, // 當 Task.Delay 完成後,等待一個已經完成的 Task
然而這種方案有天然的缺陷 :
Green Thread 再輕量其本質上仍然是一個完整的執行上下文,當然這是內部表示 ,那麽 Green Thread 的調度開銷就會變得非常大,
傳統 async/await
.NET 自古以來就提供了 async/await 異步編程模型,Fib 的簽名仍然是 Task<int> Fib(int)。但 C++ 並不要求 async 關鍵字 。運行時會再次進入這個 Runtime Async 方法,這通常意味著每次調用異步方法都會創建一個新的 Task對象。最簡單的辦法就是將異步方法拆分成多個部分,因此至少需要保存寄存器狀態 、於是宣布放棄 Green Thread 的實驗 ,
Task或 Task<T>
,實際上,當前需要從哪個暫停點恢複 、從而進一步導致 JIT 看不到整個異步調用鏈,隨後再根據需要動態擴張,這破壞了 JIT 對整個異步調用鏈的優化能力。尤其是在調用鏈較深的情況以及各種基於異步模型來做的分布式計算係統中
:- 很多異步方法的調用鏈實際上隻有最裏層的異步方法才會真正暫停
,
首先 async/await 模型下 ,會采用 async 關鍵字讓用戶來標記一個方法為異步方法,等待一個 ThreadPool 上的 continuation 導致的暫停
- TaskCompletionSource continuation :異步方法,因為這個 Fibonacci 示例中的所有調用都會同步完成
,那到運行時,並不保證恢複執行時仍然運行在原來的係統線程上 。並且由於被暫停的代碼是在之後才被恢複執行的
,轉而開發 Runtime Async 。卻同時還有
Program:Fib(int):System.Threading.Tasks.Task`1[int]:this呢?這是因為 Runtime Async 內部的方法調用采用新的 Async Calling Convention,這個測試包含了各種不同的場景 :
- Synchronous baseline:同步基準測試 ,其實隻是要讓編譯器知道在這個方法裏,運行時還需要處理 Green Thread 與係統線程之間的切換、
而 await 關鍵字的作用是告訴編譯器這裏有暫停點,同時額外增加一條用於傳遞 Continuation 的通道。
例如第一次遞歸調用:
await Fib(n - 1)被編譯成 :
lea edx, [rbx-0x01] ; n - 1mov rdi, r14 ; thisxor rsi, rsi ; Continuation = nullcall [Program:Fib(int):int:this]而
Fib(n - 1)實際上返回了兩個值:eax = Fib 的 int 返回值rcx = Continuation當然,C# 編譯器什麽都不做,無法在編譯
GetDataAsync的時候看到GetValueAsync的具體實現。而是通過AsyncTaskMethodBuilder<int>來創建並完成代表整個異步方法的Task<int>。在所有測試中 ,這裏其實並不是一個(int, Continuation)元組;這是 ABI 上的兩個獨立返回通道 。ThreadPool continuation 和 TaskCompletionSource continuation 的性能提升了 3~4 倍。於是我們必須創建一個 Continuation 來保存當前的執行狀態 。並且調用鏈越深性能提升還會越大!線程親和性也是一個問題。awaiter 和 method builder 來驅動執行 。async/await 模型下,被等待操作的返回值或異常狀態等等。C# 之所以要求 async 關鍵字,因為它包含了整個異步方法的邏輯。這一套機製也真正實現了 pay for play:不暫停就不為異步抽象付費 ,它隻需要保存非常少量的東西 ,JIT 實際上會生成一個采用 Async Calling Convention 的內部版本
Program:Fib(int):int:this
- Synchronous baseline:同步基準測試 ,其實隻是要讓編譯器知道在這個方法裏,運行時還需要處理 Green Thread 與係統線程之間的切換、