在循环中,是否使用task';将每个异步调用链接到返回的任务;我们继续

本文关键字:链接 调用 异步 返回 继续 我们 任务 是否 循环 task | 更新日期: 2023-09-27 18:24:42

最佳做法是在循环内的集合中收集所有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设置为零,循环时代码进入,检查条件
  3. 调用SomeNetworkCallAsync,等待返回的任务
  4. 创建新任务/不可用
  5. 新任务返回给CallLoopAsync()

现在,如果有足够的时间让进程生存,那么接下来的代码行(如count++SomeNetworkCallAsync)将如何/以何种方式执行?

更新-基于Jon Hanna和Stephen Cleary:

因此,有一项任务,该任务的实施将涉及对NetworkCallAsync的5次调用,但使用状态机意味着这些任务不需要显式地链接就可以工作。这个,为了例如,允许它根据取决于任务的结果等等。

尽管它们没有被链接,但每个调用都将等待上一个调用完成,因为我们已经使用了await(在状态m/c中,awaiter.GetResult();)。它的行为就像已经进行了五个连续的调用,并且它们被一个接一个地执行(只有前一个调用之后的才能完成)。如果这是真的,那么我们在编写异步调用时必须更加小心。例如:

而不是写

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
}

这是正确的吗?-对除了"一路异步","一路累积任务是一种很好的做法。类似的讨论也在这里。

在循环中,是否使用task';将每个异步调用链接到返回的任务;我们继续

当计数为零时,由于等待将创建新任务并返回

没有。不会的。因此,它将简单地调用async方法,而不存储或返回结果。loopReturnedTask中的值将存储LoopAsyncTask,与SomeNetworkCallAsync无关。

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

您可能想阅读有关async''await的MSDN文章。

要生成类似于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<TaskAwaiter, TestAsync.LoopAsyncStateMachine>(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<T>)。

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

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

因此,有一个Task,该Task的实现将涉及对NetworkCallAsync的5个调用,但使用状态机意味着这些任务不需要显式地链接才能工作。例如,这允许它根据任务的结果来决定是否中断循环,等等

async方法首次在await处产生时,它将返回Task(或Task<T>)。这是而不是await正在观察的任务;它是由CCD_ 53方法创建的完全不同的任务。async状态机控制该Task的寿命。

一种方法是将返回的Task视为表示方法本身。只有当方法完成时,返回的Task才会完成。如果该方法返回一个值,那么该值将被设置为任务的结果。如果该方法抛出异常,那么该异常将被状态机捕获并放置在该任务上。

因此,没有必要将continuation附加到返回的任务。在方法完成之前,返回的任务不会完成。

如何/以何种方式执行下一个代码行,如count++和进一步的SomeNetworkCallAsync?

我确实在async的介绍文章中对此进行了解释。总之,当方法await s时,它捕获"当前上下文"(SynchronizationContext.Current,除非它是null,在这种情况下它使用TaskScheduler.Current)。当await完成时,它将在该上下文中恢复执行其async方法。

从技术上讲就是这样;但在绝大多数情况下,这只是意味着:

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