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

Sar*_*nan 5 c# asynchronous async-await

最好的做法是收集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++;
    }
}
Run Code Online (Sandbox Code Playgroud)

我假设的步骤是

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

现在,如果有足够的时间让流程生存,那么下一个代码行将如何/以何种方式执行count++并进一步SomeNetworkCallAsync执行?

更新 - 基于Jon HannaStephen Cleary:

因此,有一个任务,该任务的实现将涉及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
}
Run Code Online (Sandbox Code Playgroud)

应该写

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 };
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Jon*_*nna 2

要生成类似于“what”asyncawait“do”的代码,如果这些关键字不存在,则需要类似于以下的代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

(上面的内容基于您使用asyncand时发生的情况await,但结果使用的名称不能是有效的 C# 类或字段名称,以及一些额外的属性。如果它MoveNext()提醒您一个IEnumeratorthat 并非完全无关紧要,则该机制whichawaitasync产生 anIAsyncStateMachine来实现 a在很多方面与如何产生 anTask类似)。yieldIEnumerator<T>

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

当它最终达到count5 时,它会调用构建器,该构建器设置返回SetResult()的结果。TaskLoopAsync

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