15.5.3 Task实现细节状态机的结构
Posted kikyoqiang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了15.5.3 Task实现细节状态机的结构相关的知识,希望对你有一定的参考价值。
状态机的整体结构非常简单。它总是使用显式接口实现,以实现.NET 4.5引入的 IAsync StateMachine 接口,并且只包含该接口声明的两个方法,即 MoveNext 和 SetStateMachine 。
此外,它还拥有大量私有或公共字段。 状态机的声明在折叠后如代码清单15-11所示:
1 [CompilerGenerated] 2 private struct DemoStateMachine : IAsyncStateMachine 3 { 4 // Fields for parameters 5 public IEnumerable<char> text; 6 7 // Fields for local variables 8 public IEnumerator<char> iterator; 9 public char ch; 10 public int total; 11 public int unicode; 12 13 // Fields for awaiters 14 private TaskAwaiter taskAwaiter; 15 private YieldAwaitable.YieldAwaiter yieldAwaiter; 16 17 // Common infrastructure 18 public int state; 19 public AsyncTaskMethodBuilder<int> builder; 20 private object stack; 21 22 void IAsyncStateMachine.MoveNext() 23 {..............} 24 }
在这段代码中,我将字段分割为不同的部分。我们已经知道表示原始参数的 text 字段 是由 骨架方法设置的,而 builder 和 state 字段亦是如此,三者皆是所有状态机共享的通用基础设施。
由于需在多次调用 MoveNext() 方法时保存变量的值,因此每个局部变量也同样拥有着自己 的字段 。有时局部变量只在两个特殊的 await 表达式之间使用,而无须保存在字段中,但就我 的经验来说,当前实现总是会将它们提升为字段。此外,这么做还可以改善调试体验,即使没有 代码再使用它们,也无须担心局部变量丢值了。
异步方法中使用的 awaiter 如果是值类型,则每个类型都会有一个字段与之对应,而如果是 引用类型(编译时的类型),则所有 awaiter 共享一个字段。本例有两个 await 表达式,分别使 用两个不同的 awaiter 结构类型,因此有两个字段 。如果第二个 await 表达式也使用了一个 TaskAwaiter ,或者如果 TaskAwiater 和 YieldAwiter 都是类,则只会有一个字段。由于一次 只能存活一个 awaiter ,因此即使一次只能存储一个值也没关系。我们需要在多个 await 表达式 之间传播awaiter,这样就可以在操作完成时得到结果。 有关通用的基础设施字段 ,我们已经了解了其中的 state 和 builder 。 state 用于跟踪踪 迹,这样后续操作可回到代码中正确的位置。 builder 具有很多功能,包括创建骨架方法返回的 Task 和 Task<T> ,即异步方法结束时传播的任务,其内包含有正确结果。 stack 字段略微有点晦 涩。当 await 表达式作为语句的一部分出现,并需要跟踪一些额外的状态,而这些状态又没有表 示为普通的局部变量时,才会用到 stack 字段。15.5.6节将介绍一个相关示例,该示例不会用于 代码清单15-11生成的状态机中。
编译器的所有魔法都体现在 MoveNext() 方法中,但在介绍它之前,我们先来快速浏览一下 SetStateMachine 。在每个状态机中,它都具有完全相同的实现,如下所示:
1 [DebuggerHidden] 2 void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine machine) 3 { 4 builder.SetStateMachine(machine); 5 }
简单来说,该方法的作用是:在builder内部,让一个已装箱状态机的复本保留有对自身的引 用。我不想深入介绍如何管理所有的装箱,你只需了解状态机可在必要时得到装箱,同时,异步 机制的各方面可保证在装箱后,还会一直使用这个已装箱的复本。这非常重要,因为我们使用的 是可变值类型(不寒而栗!)。如果允许对状态机的不同复本进行不同的修改,那么整个程序很快 就会崩溃。
换一个角度来说(如果你开始认真思考状态机的实例变量是如何传播的,这就会变得很重 要),状态机之所以设计为 struct ,就是为了避免早期不必要的堆分配,但大多数代码都将其视 作一个类。围绕 SetStateMachine 的那些引用,让这一切正常运作。
所有代码如下:
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.3 Task实现细节状态机的结构的主要内容,如果未能解决你的问题,请参考以下文章
.NET Task 揭秘async 与 AsyncMethodBuilder