15.5.2 【Task實現細節】骨架方法的結構
盡管骨架方法中的代碼非常簡單,但它暗示了狀態機的職責。代碼清單15-11生成的骨架方 法如下所示:
1 [DebuggerStepThrough] 2 [AsyncStateMachine(typeof(DemoStateMachine))] 3 static Task<int> SumCharactersAsync(IEnumerable<char> text) 4 { 5 var machine = new DemoStateMachine(); 6machine.text = text; 7 machine.builder = AsyncTaskMethodBuilder<int>.Create(); 8 machine.state = -1; 9 machine.builder.Start(ref machine); 10 return machine.builder.Task; 11 }
AsyncStateMachineAttribute 類型是為 async 引入的新特性(attribute)之一。它是為工具而設計的,你自己並不會有機會消費這個特性,並且也不應該在自己的方法上應用這個特性。
? 一個是參數( text )。顯然有多少個參數就會有多少個字段。
? 一個是 AsyncTaskMethodBuilder<int> 。該結構負責將狀態機和骨架方法聯系在一起。對於僅返回 Task 的方法,存在對應的非泛型類。對於返回 void 的方法,可以使用AsnycVoidMethodBuilder 結構。
? 一個是 state ,值從 -1 開始。初始值永遠為 -1 ,稍後我們會介紹其他值的含義。
由於狀態機是一個結構(struct), AsyncTaskMethodBuilder<int> 也是一個結構,因此我 們還沒有執行任何堆分配。當然,完全可以讓執行的不同調用在堆上進行分配,但有必要指出的 是,代碼在盡可能地避免這麽做。異步的本質意味著,如果哪個 await 表達式需要真正的等待, 你會需要很多這種值(在堆上),但代碼保證了它們只會在需要的時候進行裝箱。所有這些都屬 於實現細節,就像堆和棧屬於實現細節一樣,但為了讓 async 能夠適用於盡可能多的場景,微軟 的相關團隊緊密合作,將分配降低到了絕對最小值。
對 machine.builder.Start(ref machine) 的調用非常有意思。這裏使用了按引用傳遞, 以避免創建狀態機的復本(以及builder的復本),這是出於性能和正確性兩方面的考慮。編譯器 非常願意將狀態機和builder視為類,因此 ref 可以在代碼中自由地使用。為了使用接口,不同的 方法將builder(或awaiter)作為參數,使用泛型類型參數,並限定其實現某個接口(如對於狀態 機來說就是 IAsyncStateMachine )。這樣在調用接口的成員時,就不需要任何裝箱了。方法的 行為描述起來非常簡單——它讓狀態機同步地執行第一個步驟,並在方法完成時或到達需等待的 異步操作點時得以返回。
第一個步驟完成後,骨架方法將返回builder中的任務。狀態機在結束時,會使用builder來設 置結果或異常。
1 class DecompilationSampleDecompiled 2 { 3 static void Main() 4 { 5 Task<int> task = SumCharactersAsync("test"); 6 Console.WriteLine(task.Result); 7 } 8 9 [DebuggerStepThrough] 10 [AsyncStateMachine(typeof(DemoStateMachine))] 11 static Task<int> SumCharactersAsync(IEnumerable<char> text) 12 { 13 var machine = new DemoStateMachine(); 14 machine.text = text; 15 machine.builder = AsyncTaskMethodBuilder<int>.Create(); 16 machine.state = -1; 17 machine.builder.Start(ref machine); 18 return machine.builder.Task; 19 } 20 21 [CompilerGenerated] 22 private struct DemoStateMachine : IAsyncStateMachine 23 { 24 // Fields for parameters 25 public IEnumerable<char> text; 26 27 // Fields for local variables 28 public IEnumerator<char> iterator; 29 public char ch; 30 public int total; 31 public int unicode; 32 33 // Fields for awaiters 34 private TaskAwaiter taskAwaiter; 35 private YieldAwaitable.YieldAwaiter yieldAwaiter; 36 37 // Common infrastructure 38 public int state; 39 public AsyncTaskMethodBuilder<int> builder; 40 private object stack; 41 42 void IAsyncStateMachine.MoveNext() 43 { 44 int result = default(int); 45 try 46 { 47 bool doFinallyBodies = true; 48 switch (state) 49 { 50 case -3: 51 goto Done; 52 case 0: 53 goto FirstAwaitContinuation; 54 case 1: 55 goto SecondAwaitContinuation; 56 } 57 // Default case - first call (state is -1) 58 total = 0; 59 iterator = text.GetEnumerator(); 60 61 // We really want to jump straight to FirstAwaitRealContinuation, but we can‘t 62 // goto a label inside a try block... 63 FirstAwaitContinuation: 64 // foreach loop 65 try 66 { 67 // for/foreach loops typically have the condition at the end of the generated code. 68 // We want to go there *unless* we‘re trying to reach the first continuation. 69 if (state != 0) 70 { 71 goto LoopCondition; 72 } 73 goto FirstAwaitRealContinuation; 74 LoopBody: 75 ch = iterator.Current; 76 unicode = ch; 77 TaskAwaiter localTaskAwaiter = Task.Delay(unicode).GetAwaiter(); 78 if (localTaskAwaiter.IsCompleted) 79 { 80 goto FirstAwaitCompletion; 81 } 82 state = 0; 83 taskAwaiter = localTaskAwaiter; 84 builder.AwaitUnsafeOnCompleted(ref localTaskAwaiter, ref this); 85 doFinallyBodies = false; 86 return; 87 FirstAwaitRealContinuation: 88 localTaskAwaiter = taskAwaiter; 89 taskAwaiter = default(TaskAwaiter); 90 state = -1; 91 FirstAwaitCompletion: 92 localTaskAwaiter.GetResult(); 93 localTaskAwaiter = default(TaskAwaiter); 94 total += unicode; 95 LoopCondition: 96 if (iterator.MoveNext()) 97 { 98 goto LoopBody; 99 } 100 } 101 finally 102 { 103 if (doFinallyBodies && iterator != null) 104 { 105 iterator.Dispose(); 106 } 107 } 108 109 // After the loop 110 YieldAwaitable.YieldAwaiter localYieldAwaiter = Task.Yield().GetAwaiter(); 111 if (localYieldAwaiter.IsCompleted) 112 { 113 goto SecondAwaitCompletion; 114 } 115 state = 1; 116 yieldAwaiter = localYieldAwaiter; 117 builder.AwaitUnsafeOnCompleted(ref localYieldAwaiter, ref this); 118 doFinallyBodies = false; 119 return; 120 121 SecondAwaitContinuation: 122 localYieldAwaiter = yieldAwaiter; 123 yieldAwaiter = default(YieldAwaitable.YieldAwaiter); 124 state = -1; 125 SecondAwaitCompletion: 126 localYieldAwaiter.GetResult(); 127 localYieldAwaiter = default(YieldAwaitable.YieldAwaiter); 128 result = total; 129 } 130 catch (Exception ex) 131 { 132 state = -2; 133 builder.SetException(ex); 134 return; 135 } 136 Done: 137 state = -2; 138 builder.SetResult(result); 139 } 140 141 [DebuggerHidden] 142 void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine machine) 143 { 144 builder.SetStateMachine(machine); 145 } 146 } 147 }
15.5.2 【Task實現細節】骨架方法的結構