在循环内部,每个异步调用是否使用task的continuewith链接到返回的任务?

最佳实践是收集循环内集合中的所有async调用,并执行Task.WhenAll() 。 但是,想要了解在循环中遇到await时会发生什么,返回的Task包含什么? 那些进一步的async调用呢? 它会创建新任务并按顺序将它们添加到已返回的Task吗?

根据下面的代码

 private void CallLoopAsync() { var loopReturnedTask = LoopAsync(); } private async Task LoopAsync() { int count = 0; while(count < 5) { await SomeNetworkCallAsync(); count++; } } 

我假设的步骤是

  1. LoopAsync被调用
  2. count设置为零,代码进入while循环,条件被检查
  3. SomeNetworkCallAsync ,等待返回的任务
  4. 创建新任务/等待
  5. 新任务返回CallLoopAsync ()

现在,只要有足够的时间让进程SomeNetworkCallAsync ,如何/以何种方式,将执行下一个代码行如count++和更多的SomeNetworkCallAsync

更新 – 基于Jon Hanna和Stephen Cleary :

所以有一个Task,该Task的实现将涉及5次对NetworkCallAsync的调用,但是使用状态机意味着这些任务不需要明确地链接到这个就可以工作。 例如,这允许它根据任务的结果决定是否中断循环,等等。

虽然它们没有链接,但每个调用都会等待前一个调用完成,因为我们使用了await (在状态m / c, awaiter.GetResult(); )。 它的行为好像已经进行了五次连续调用并且它们一个接一个地执行(仅在前一次调用完成之后 )。 如果这是真的,我们必须更加小心我们如何组成异步调用。对于ex:

而不是写作

 private async Task SomeWorkAsync() { await SomeIndependentNetworkCall();// 2 sec to complete var result1 = await GetDataFromNetworkCallAsync(); // 2 sec to complete await PostDataToNetworkAsync(result1); // 2 sec to complete } 

应该写

 private Task[] RefactoredSomeWorkAsync() { var task1 = SomeIndependentNetworkCall();// 2 sec to complete var task2 = GetDataFromNetworkCallAsync() .ContinueWith(result1 => PostDataToNetworkAsync(result1)).Unwrap();// 4 sec to complete return new[] { task1, task2 }; } 

因此,我们可以说RefactoredSomeWorkAsync更快2秒,因为并行性的可能性

 private async Task CallRefactoredSomeWorkAsync() { await Task.WhenAll(RefactoredSomeWorkAsync());//Faster, 4 sec await SomeWorkAsync(); // Slower, 6 sec } 

它是否正确? – 是的 除了“ 一路异步 ”,“一直累积任务 ”是一种很好的做法。 类似的讨论在这里

要生成类似于asyncawait代码,如果这些关键字不存在,则需要代码类似:

 private struct LoopAsyncStateMachine : IAsyncStateMachine { public int _state; public AsyncTaskMethodBuilder _builder; public TestAsync _this; public int _count; private TaskAwaiter _awaiter; void IAsyncStateMachine.MoveNext() { try { if (_state != 0) { _count = 0; goto afterSetup; } TaskAwaiter awaiter = _awaiter; _awaiter = default(TaskAwaiter); _state = -1; loopBack: awaiter.GetResult(); awaiter = default(TaskAwaiter); _count++; afterSetup: if (_count < 5) { awaiter = _this.SomeNetworkCallAsync().GetAwaiter(); if (!awaiter.IsCompleted) { _state = 0; _awaiter = awaiter; _builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); return; } goto loopBack; } _state = -2; _builder.SetResult(); } catch (Exception exception) { _state = -2; _builder.SetException(exception); return; } } [DebuggerHidden] void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0) { _builder.SetStateMachine(param0); } } public Task LoopAsync() { LoopAsyncStateMachine stateMachine = new LoopAsyncStateMachine(); stateMachine._this = this; AsyncTaskMethodBuilder builder = AsyncTaskMethodBuilder.Create(); stateMachine._builder = builder; stateMachine._state = -1; builder.Start(ref stateMachine); return builder.Task; } 

(以上是基于使用asyncawait时发生的情况,除了它的结果使用的名称不能是有效的C#类或字段名称,以及一些额外的属性。如果它的MoveNext()提醒你一个IEnumerator不是完全不相关, awaitasync生成IAsyncStateMachine来实现Task的机制在许多方面类似于yield如何生成IEnumerator

结果是来自AsyncTaskMethodBuilder的单个Task,并使用LoopAsyncStateMachine (它接近async生成的隐藏struct )。 它的MoveNext()方法首先在任务启动时调用。 然后它将在SomeNetworkCallAsync上使用SomeNetworkCallAsync 。 如果它已经完成,它将继续进入下一阶段(增量count等),否则它将awaiter存储在一个字段中。 在后续使用时,它将被调用,因为SomeNetworkCallAsync()任务已经返回,它将获得结果(在这种情况下为空,但如果返回值则可以是值)。 然后它会尝试进一步循环,并在等待尚未完成的任务时再次返回。

当它最终达到5的count ,它在构建器上调用SetResult() ,它设置LoopAsync返回的Task的结果。

所以有一个Task ,该Task的实现将涉及5次对NetworkCallAsync调用,但是使用状态机意味着这些任务不需要明确地链接到这个就可以工作。 例如,这允许它根据任务的结果决定是否中断循环,等等。

当count为零时,将创建新任务,因为等待并返回

不,它不会。 它将简单地调用async方法,而不存储或返回结果。 loopReturnedTask的值将存储loopReturnedTaskTask ,与SomeNetworkCallAsync

 await SomeNetworkCallAsync(); // call, wait and forget the result 

您可能希望阅读有关async \ await的MSDN文章 。

async方法首先在await处产生时,它返回一个Task (或Task )。 这不是 await所遵循的任务; 它是由async方法创建的完全不同的任务。 async状态机控制该Task的生命周期。

考虑它的一种方法是将返回的Task视为表示方法本身。 返回的Task仅在方法完成时才会完成。 如果方法返回值,则将该值设置为任务的结果。 如果该方法抛出exception,则状态机将捕获该exception并将其置于该任务上。

因此,不需要为返回的任务附加延续。 在方法完成之前,返回的任务将不会完成。

如何/以什么方式,将执行下一个代码行如count ++和更多的SomeNetworkCallAsync?

我在async介绍中解释了这一点。 总之,当一个方法await s时,它捕获一个“当前上下文”( SynchronizationContext.Current除非它为null ,在这种情况下它使用TaskScheduler.Current )。 当await完成时,它将继续在该上下文中执行其async方法。

这就是技术上发生的事情; 但在绝大多数情况下,这仅仅意味着:

  • 如果async方法在UI线程上启动,那么它将在同一个UI线程上恢复。
  • 如果async方法在ASP.NET请求上下文中启动,那么它将使用相同的请求上下文(不一定在同一个线程上)继续。
  • 否则, async方法将在线程池线程上恢复。