在循环内,是否使用任务的continuewith将每个异步调用链接到返回的任务? [英] Inside a loop,does each async call get chained to the returned task using task's continuewith?

查看:67
本文介绍了在循环内,是否使用任务的continuewith将每个异步调用链接到返回的任务?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最佳实践是在循环内的集合中收集所有async调用,然后执行Task.WhenAll().但是,想了解在循环内遇到await时会发生什么,返回的Task将包含什么?接下来的async调用怎么办?它会创建新任务并将它们依次添加到已经返回的Task吗?

The best practice is to collect all the async calls in a collection inside the loop and do Task.WhenAll(). Yet, want to understand what happens when an await is encountered inside the loop, what would the returned Task contain? what about further async calls? Will it create new tasks and add them to the already returned Task sequentially?

按照下面的代码

private void CallLoopAsync()
{
   var loopReturnedTask = LoopAsync();
}

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

我假设的步骤是

  1. LoopAsync被呼叫
  2. count设置为零,循环时输入代码,检查条件
  3. 调用
  4. SomeNetworkCallAsync,等待返回的任务
  5. 已创建新任务/可等待任务
  6. 新任务返回到CallLoopAsync()
  1. LoopAsync gets called
  2. count is set to zero, code enters while loop, condition is checked
  3. SomeNetworkCallAsync is called,and the returned task is awaited
  4. New task/awaitable is created
  5. New task is returned to CallLoopAsync()

现在,只要有足够的时间使进程运行,如何/以何种方式执行下一个代码行,如count++和进一步的SomeNetworkCallAsync?

Now, provided there is enough time for the process to live, How / In what way, will the next code lines like count++ and further SomeNetworkCallAsync be executed?

更新-基于乔恩·汉纳

因此,只有一项任务,该任务的实施将涉及 5个调用NetworkCallAsync,但使用状态机的方式 这些任务无需显式链接即可运行.这个,对于 例如,允许它根据是否决定中断循环 任务的结果等等.

So there is one Task and the implementation of that Task will involve 5 calls to NetworkCallAsync, but the use of a state-machine means those tasks need not be explicitly chained for this to work. This, for example, allows it to decide whether to break the looping or not based on the result of a task, and so on.

尽管它们未链接在一起,但每个调用都将等待上一个调用完成,因为我们已经使用了await(处于状态m/c,awaiter.GetResult();).它的行为就好像已经进行了五个连续调用,并且一个接一个地执行(仅在先前调用之后完成后 ).如果这是真的,那么我们在编写异步调用的方式上必须更加谨慎.例如:

Though they are not chained, each call will wait for the previous call to complete as we have used await (in state m/c, awaiter.GetResult();). It behaves as if five consecutive calls have been made and they are executed one after the another (only after the previous call gets completed). If this is true, we have to be bit more careful in how we are composing the async calls.For 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 };
}

由于 parallelism

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

这是正确的吗? - 是的.除了"一路异步"之外,"一路累积任务"也是一种很好的做法.类似的讨论是此处

Is this correct? - Yes. Along with "async all the way", "Accumulate tasks all the way" is good practice. Similar discussion is here

推荐答案

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

To produce code similar to what async and await do, if those keywords didn't exist, would require code a bit like:

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()让您想起不是完全无关的IEnumeratorawaitasync产生IAsyncStateMachine来实现Task的机制在许多方面与yield产生IEnumerator<T>的方式相似)

(The above is based on what happens when you use async and await except that the result of that uses names that cannot be valid C# class or field names, along with some extra attributes. If its MoveNext() reminds you of an IEnumerator that's not entirely irrelevant, the mechanism by which await and async produce an IAsyncStateMachine to implement a Task is similar in many ways to how yield produces an IEnumerator<T>).

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

The result is a single Task which comes from AsyncTaskMethodBuilder and makes use of LoopAsyncStateMachine (which is close to the hidden struct that the async produces). Its MoveNext() method is first called upon the task being started. It will then use an awaiter on SomeNetworkCallAsync. If it is already completed it moves on to the next stage (increment count and so on), otherwise it stores the awaiter in a field. On subsequent uses it will be called because the SomeNetworkCallAsync() task has returned, and it will get the result (which is void in this case, but could be a value if values were returned). It then attempts further loops and again returns when it is waiting on a task that is not yet completed.

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

When it finally reaches a count of 5 it calls SetResult() on the builder, which sets the result of the Task that LoopAsync had returned.

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

So there is one Task and the implementation of that Task will involve 5 calls to NetworkCallAsync, but the use of a state-machine means those tasks need not be explicitly chained for this to work. This, for example, allows it to decide whether to break the looping or not based on the result of a task, and so on.

这篇关于在循环内,是否使用任务的continuewith将每个异步调用链接到返回的任务?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆