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

时间:2015-11-26 05:44:12

标签: 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++;
    }
}

我假设的步骤是

  1. LoopAsync被称为
  2. count设置为零,代码进入while循环,条件被检查
  3. SomeNetworkCallAsync被调用,等待返回的任务
  4. 创建新任务/等待
  5. 新任务将返回CallLoopAsync()
  6. 现在,只要有足够的时间让流程生效,如何/以何种方式执行下一个代码行,如count++和进一步SomeNetworkCallAsync

    更新 - 基于Jon HannaStephen Cleary

      

    所以有一个任务,该任务的实现将涉及   5调用NetworkCallAsync,但使用状态机意味着   这些任务不需要明确地链接到这个工作。这个,为   例如,允许它决定是否打破循环   关于任务的结果,等等。

    虽然它们没有链接,但每次调用都会等待上一次调用完成,因为我们使用了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
    }
    

    这是对的吗? - 是的除了“异步”,“累计任务”是一种很好的做法。类似的讨论是here

3 个答案:

答案 0 :(得分:3)

  

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

没有。它不会。它将简单地调用async方法,而不存储或返回结果。 loopReturnedTask中的值将存储Task的{​​{1}},与LoopAsync无关。

SomeNetworkCallAsync

您可能需要阅读MSDN article on async\await

答案 1 :(得分:2)

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

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

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

答案 2 :(得分:1)

async方法首先在await得到时,它会返回Task(或Task<T>)。这是 await正在观察的任务;它是async方法创建的完全不同的任务。 async状态机控制Task的生命周期。

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

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

  

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

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

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

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