猿问

如何在.NET中产生并等待实现控制流?

据我了解yield,如果从迭代器块内部使用该关键字,它将控制流返回到调用代码,并且当再次调用该迭代器时,它将从中断的地方开始。


同样,await不仅要等待被调用方,而且还会将控制权返回给调用方,仅在调用方awaits方法时从中断处接管。


换句话说,没有线程,异步和等待的“并发性”是由聪明的控制流引起的错觉,其细节被语法隐藏了。


现在,我是一名前汇编程序员,并且对指令指针,堆栈等非常熟悉,并且了解了正常的控制流程(子例程,递归,循环,分支)的工作方式。但是这些新结构-我不明白。


当await到达,如何运行时知道什么是一段代码下一步应该执行?它如何知道何时可以从上次中断的地方恢复,以及如何记住在哪里?当前调用堆栈发生了什么,是否以某种方式保存了它?如果调用方法在此之前进行其他方法调用await怎么办-为什么堆栈不被覆盖?在异常和堆栈展开的情况下,运行时到底将如何处理所有这些问题?


何时yield到达,运行时如何跟踪应该拾取的点?迭代器状态如何保存?


慕斯709654
浏览 482回答 3
3回答

梵蒂冈之花

yield 是两者中比较容易的一个,所以让我们检查一下。说我们有:public IEnumerable<int> CountToTen(){&nbsp; for (int i = 1; i <= 10; ++i)&nbsp; {&nbsp; &nbsp; yield return i;&nbsp; }}这被编译一个位,如果我们想这样写的:// Deliberately use name that isn't valid C# to not clash with anythingprivate class <CountToTen> : IEnumerator<int>, IEnumerable<int>{&nbsp; &nbsp; private int _i;&nbsp; &nbsp; private int _current;&nbsp; &nbsp; private int _state;&nbsp; &nbsp; private int _initialThreadId = CurrentManagedThreadId;&nbsp; &nbsp; public IEnumerator<CountToTen> GetEnumerator()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; // Use self if never ran and same thread (so safe)&nbsp; &nbsp; &nbsp; &nbsp; // otherwise create a new object.&nbsp; &nbsp; &nbsp; &nbsp; if (_state != 0 || _initialThreadId != CurrentManagedThreadId)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return new <CountToTen>();&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; _state = 1;&nbsp; &nbsp; &nbsp; &nbsp; return this;&nbsp; &nbsp; }&nbsp; &nbsp; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();&nbsp; &nbsp; public int Current => _current;&nbsp; &nbsp; object IEnumerator.Current => Current;&nbsp; &nbsp; public bool MoveNext()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; switch(_state)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 1:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _i = 1;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _current = i;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _state = 2;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 2:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ++_i;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (_i <= 10)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _current = _i;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; _state = -1;&nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; }&nbsp; &nbsp; public void Dispose()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; // if the yield-using method had a `using` it would&nbsp; &nbsp; &nbsp; // be translated into something happening here.&nbsp; &nbsp; }&nbsp; &nbsp; public void Reset()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; throw new NotSupportedException();&nbsp; &nbsp; }}所以,还不如一个手写的执行效率IEnumerable<int>和IEnumerator<int>(例如,我们可能不会有一个单独的浪费_state,_i而且_current在这种情况下),但不坏(的伎俩再利用自身安全情况下这样做,而不是创建一个新的对象是好的),并且可以扩展以处理非常复杂的yield使用方法。当然,因为foreach(var a in b){&nbsp; DoSomething(a);}是相同的:using(var en = b.GetEnumerator()){&nbsp; while(en.MoveNext())&nbsp; {&nbsp; &nbsp; &nbsp;var a = en.Current;&nbsp; &nbsp; &nbsp;DoSomething(a);&nbsp; }}然后,生成的MoveNext()被重复调用。这种async情况几乎是相同的原理,但是有一些额外的复杂性。重用另一个答案代码中的示例,例如:private async Task LoopAsync(){&nbsp; &nbsp; int count = 0;&nbsp; &nbsp; while(count < 5)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;await SomeNetworkCallAsync();&nbsp; &nbsp; &nbsp; &nbsp;count++;&nbsp; &nbsp; }}产生如下代码:private struct LoopAsyncStateMachine : IAsyncStateMachine{&nbsp; public int _state;&nbsp; public AsyncTaskMethodBuilder _builder;&nbsp; public TestAsync _this;&nbsp; public int _count;&nbsp; private TaskAwaiter _awaiter;&nbsp; void IAsyncStateMachine.MoveNext()&nbsp; {&nbsp; &nbsp; try&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; if (_state != 0)&nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; _count = 0;&nbsp; &nbsp; &nbsp; &nbsp; goto afterSetup;&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; TaskAwaiter awaiter = _awaiter;&nbsp; &nbsp; &nbsp; _awaiter = default(TaskAwaiter);&nbsp; &nbsp; &nbsp; _state = -1;&nbsp; &nbsp; loopBack:&nbsp; &nbsp; &nbsp; awaiter.GetResult();&nbsp; &nbsp; &nbsp; awaiter = default(TaskAwaiter);&nbsp; &nbsp; &nbsp; _count++;&nbsp; &nbsp; afterSetup:&nbsp; &nbsp; &nbsp; if (_count < 5)&nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; awaiter = _this.SomeNetworkCallAsync().GetAwaiter();&nbsp; &nbsp; &nbsp; &nbsp; if (!awaiter.IsCompleted)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _state = 0;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _awaiter = awaiter;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _builder.AwaitUnsafeOnCompleted<TaskAwaiter, TestAsync.LoopAsyncStateMachine>(ref awaiter, ref this);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; goto loopBack;&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; _state = -2;&nbsp; &nbsp; &nbsp; _builder.SetResult();&nbsp; &nbsp; }&nbsp; &nbsp; catch (Exception exception)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; _state = -2;&nbsp; &nbsp; &nbsp; _builder.SetException(exception);&nbsp; &nbsp; &nbsp; return;&nbsp; &nbsp; }&nbsp; }&nbsp; [DebuggerHidden]&nbsp; void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0)&nbsp; {&nbsp; &nbsp; _builder.SetStateMachine(param0);&nbsp; }}public Task LoopAsync(){&nbsp; LoopAsyncStateMachine stateMachine = new LoopAsyncStateMachine();&nbsp; stateMachine._this = this;&nbsp; AsyncTaskMethodBuilder builder = AsyncTaskMethodBuilder.Create();&nbsp; stateMachine._builder = builder;&nbsp; stateMachine._state = -1;&nbsp; builder.Start(ref stateMachine);&nbsp; return builder.Task;}它比较复杂,但是基本原理非常相似。最主要的复杂之处在于现在GetAwaiter()正在使用它。如果awaiter.IsCompleted检查了任何时间,则true由于任务awaited已经完成(例如,它可以同步返回),该方法将返回,然后该方法将继续遍历状态,否则将其自身设置为对等待者的回调。究竟发生什么取决于等待者,包括触发回调的原因(例如异步I / O完成,在线程上运行的任务完成)以及对编组到特定线程或在线程池线程上运行有什么要求,可能需要也可能不需要原始调用的上下文,依此类推。无论该等待者中的内容是什么,都会调用MoveNext,它将继续进行下一个工作(直到下一个工作await),或者完成并返回,在这种情况下Task,实现的对象将完成。
随时随地看视频慕课网APP
我要回答