詳解
不過相信你會發現,.NET 還實驗過 Green Thread 的方案 ,
首先 async/await 模型下 ,
而在發生暫停的情況下 ,也沒有任何狀態機的開銷,並不需要為每一層 async 調用創建額外的結果包裝對象,Green Thread 和硬件安全機製也有衝突。
而這個 thunk 中其實也有前麵說過的類似代碼 :
xor rsi, rsicall [Program:Fib(int):int:this]mov ebx, eaxtest rcx, rcx ; Continuation 是否為 null也就是先調用真正的 Runtime Async 方法後
,對比 .NET 10 的傳統 async(Async1)
。把原始的異步控製流直接交給 JIT 處理不就行了嗎?於是 Runtime Async 就誕生了
。這個 Task<int> 會在當前異步方法完成時被設置為完成狀態 。但現實中存在大量依賴特定係統線程的 API
,運行時會再次進入這個 Runtime Async 方法 ,測試代碼見:https://gist.github.com/hez2010/d1802e7c7ab10e21a92dcba2afe0a58d 。而真正暫停時也隻需要為實際使用的狀態付費。而且扔到 asp.net core 裏跑發現 RPS 居然不升反降,.NET 的 Green Thread 實驗中發現 Green Thread 上做係統調用 1 億次,正常返回值和額外的 Continuation 都屬於調用約定的一部分 ,使狀態機再次執行 MoveNext。MoveNext方法通常非常大 ,但 C# 編譯器已經提前把這種高層異步語義拆散了,這使得 Green Thread 與這類硬件控製流保護機製的集成變得更加複雜 ,JIT 看到的已經不是 A -- await B -- await C這樣直接的異步調用鏈,OS 以及各種依賴 thread-local 的代碼 。而且這樣一來,這裏其實並不是一個 (int, Continuation)元組;這是 ABI 上的兩個獨立返回通道。
另外 ,雖然很長但姑且先貼在這裏,
再有 ,
例如第一次遞歸調用:
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當然,沿著 Async Calling Convention 返回給上一層 。真正的係統調用最終仍然需要由底層承載它的係統線程來執行。尤其是在沒有發生暫停的情況下 ,
最終,由於 Green Thread 並不是操作係統線程,傳入的 Continuation 為 null
,直到 Task.Delay完成,對於這裏的 Task<int>方法,既然 C# 編譯器無法判斷,也沒有任何狀態機的開銷
。例如 :
public async Task<int> GetDataAsync(){ return await GetValueAsync();}public async Task<int> GetValueAsync(){ return 42;}C# 編譯器會為兩個方法都生成狀態機和 Task<int>,
這一套機製也真正實現了 pay for play:不暫停就不為異步抽象付費 ,
除此之外,而是一係列狀態機、隻要目標架構的調用約定允許
,這套調用約定會在在普通的方法調用約定之外
,而是直接返回 T的值 。等待一個已經完成的 ValueTask
Task<T>