【问题标题】:When does Task.WhenAll enumerate?Task.WhenAll 何时枚举?
【发布时间】:2017-10-01 08:59:44
【问题描述】:

我对异步编程很陌生。我想在等待任何一个任务之前启动一系列任务(发出 http 请求)。

List<Guid> identifiers;

//Set identifiers to what they should be

var task = Task.WhenAll(identifiers.Select(id => _serviceConnector.GetAsync(id)));

// Call and await another request

await task;

我的问题是:我的 http 请求会随着通过 Task.WhenAll 创建任务而启动吗?或者它们不会在等待进一步下降之前启动?谢谢!

【问题讨论】:

    标签: linq asynchronous task


    【解决方案1】:

    我的 http 请求会随着任务的创建而启动吗 通过Task.WhenAll?或者它们不会在 await 进一步下降之前启动?

    当您将IEnumerable&lt;Task&gt; 传递给Task.WhenAll 时,它会枚举任务序列并将它们全部列出以供进一步处理:

    if (tasks == null) throw new ArgumentNullException("tasks");
    List<Task<TResult>> taskList = new List<Task<TResult>>();
    foreach (Task<TResult> task in tasks)
    {
        if (task == null) throw new ArgumentException("tasks");
        taskList.Add(task);
    }
    

    这意味着如果你传递一个 LINQ 查询,它将被执行:

    identifiers.Select(id => _serviceConnector.GetAsync(id))
    

    但这并不意味着Task.WhenAll 开始这些任务。例如。如果您的查询将返回尚未启动的任务,那么这些任务将保持非运行状态。例如。以下查询将创建任务但不会启动它们,因此WhenAll 将等待这些任务

    var tasks = Enumerable.Range(1, 10).Select(i => new Task<int>(() => i));
    var task = Task.WhenAll(tasks);
    

    在您的情况下,一切都取决于 GetAsync(id) 方法。如果它创建并启动一个任务(就像HttpClient 所做的那样),那么所有任务都将在Task.WhenAll 调用的开头创建并启动。


    TL;DR Task.WhenAll 方法的实现细节。如上所述,它抓取所有给定任务(对于 IEnumerable 参数,它将所有任务放入列表中)并创建一个 WhenAllPromise&lt;T&gt; 类型的新任务

    private sealed class WhenAllPromise<T> : Task<T[]>, ITaskCompletionAction
    

    如您所见,此任务实现了ITaskCompletionAction。这是一个内部接口,用于为任务添加完成动作——延续任务的轻量级版本(因为它是一个简单的动作)。这个接口定义了单个方法Invoke(Task),当任务完成执行时应该调用它。 Task 有一个允许添加这些轻量级延续的内部方法:

    internal void AddCompletionAction(ITaskCompletionAction action)
    

    现在回到WhenAllPromise&lt;T&gt; 类。它有两个字段:

    private readonly Task<T>[] m_tasks;
    private int m_count;
    

    在初始化期间,该类将所有给定任务存储在字段数组中,初始化计数器,并为已完成的任务调用延续或将自身添加到任务完成操作中:

    m_tasks = tasks;
    m_count = tasks.Length;
    
    foreach (var task in tasks)
    {
        if (task.IsCompleted) this.Invoke(task); // short-circuit
        else task.AddCompletionAction(this); // simple completion action
    }
    

    就是这样。任务不是由WhenAllPromise 类启动的。它仅使用任务完成时调用的回调操作。在回调动作中,每次完成一些任务时它会减少 m_count,直到所有任务都完成并且我们可以获取结果。

    【讨论】:

      【解决方案2】:

      WhenAll 立即(并同步)具体化其可枚举参数。所以所有任务都会在WhenAll 返回时启动。

      如果您考虑一下,这是有道理的。 WhenAll 必须知道它正在等待多少个任务,以便它知道自己的任务何时完成。此外,它必须链接到每个任务,以便在每个子任务完成时收到通知。没有其他时间来做这项工作了;它必须在返回之前计数并设置通知。

      【讨论】:

      • 但是为什么WhenAll 不能只是迭代并等待每个任务呢?
      • @SerG:我想它可以,但是如果枚举对象创建被枚举的任务,它最终可能会像“异步 foreach”一样。
      猜你喜欢
      • 1970-01-01
      • 2010-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-22
      相关资源
      最近更新 更多