【问题标题】:async within a LINQ code - Clarification?LINQ 代码中的异步 - 澄清?
【发布时间】:2019-07-15 16:38:29
【问题描述】:

几乎每个 SO 对这个主题的回答都指出:

LINQ doesn't work perfectly with async

还有:

I recommend that you not think of this as "using async within LINQ"

但在斯蒂芬的书中有一个示例:

问题:你有一系列任务要等待,你想做一些 完成后对每个任务进行处理。但是,你想做 每一个的处理一旦完成,等待 任何其他任务。

推荐的解决方案之一是:

static async Task<int> DelayAndReturnAsync(int val)
{
 await Task.Delay(TimeSpan.FromSeconds(val));
 return val;
}

// This method now prints "1", "2", and "3".
static async Task ProcessTasksAsync()
{
 // Create a sequence of tasks.
 Task<int> taskA = DelayAndReturnAsync(2);
 Task<int> taskB = DelayAndReturnAsync(3);
 Task<int> taskC = DelayAndReturnAsync(1);
 var tasks = new[] { taskA, taskB, taskC };
 var processingTasks = tasks.Select(async t =>
    {
    var result = await t;
    Trace.WriteLine(result);
    }).ToArray();

// Await all processing to complete
await Task.WhenAll(processingTasks);

}

问题 #1:

我不明白为什么现在 async 在 LINQ 语句中 - 确实有效。我们不是说“不要考虑在 LINQ 中使用async”吗?

问题 #2:

当控件到达这里的await t 时——实际发生了什么?控件是否离开ProcessTasksAsync 方法?还是离开匿名方法并继续迭代?

【问题讨论】:

  • “不要考虑......”的声明并不是必须的。您引用的所有文本都没有说异步不适用于 Linq - 只是它不是完美匹配。
  • Stephen Toub 的一本好书,如果您还没有读到:Tasks, Monads, and LINQ

标签: c# .net asynchronous async-await task-parallel-library


【解决方案1】:

我不明白为什么现在 LINQ 语句中的 async 确实有效。我们不是说“不要考虑在 LINQ 中使用异步”吗?

async 大部分 不适用于 LINQ,因为 IEnumerable&lt;T&gt; 扩展并不总是正确推断委托类型并遵循 Action&lt;T&gt;。他们对Task类没有特别的了解。这意味着实际的异步委托变为async void,这很糟糕。对于Enumerable.Select,我们有一个重载,它返回一个Func&lt;T&gt;(在我们的例子中又是Func&lt;Task&gt;),相当于async Task,因此它适用于异步用例。

当控件到达此处的 await t 时——实际发生了什么?控件是否离开 ProcessTasksAsync 方法?

不,它没有。 Enumerable.Select 是关于投影序列中的所有元素。这意味着对于集合中的每个元素,await t 会将控制权交还给迭代器,迭代器将继续迭代所有元素。这就是为什么您稍后必须await Task.WhenAll,以确保所有元素都已完成执行。

【讨论】:

    【解决方案2】:

    问题 1:

    不同之处在于每个任务都是继续附加处理,即:Trace.WriteLine(result);。在您指向的链接中,该代码不会更改任何内容,只会产生等待和包装另一个任务的开销。

    问题 2:

    当控件到达此处的 await t 时——实际发生了什么?

    它等待ProcessTasksAsync 的任务结果,然后继续Trace.WriteLine(result);。可以说,当我们得到结果并且处理仍在匿名方法内部时,控件离开了ProcessTasksAsync方法。

    最后,我们有await Task.WhenAll(processingTasks);,它将等待所有任务(包括附加处理(Trace.WriteLine(result);)在继续之前完成,但每个任务不会等待其他任务继续执行:Trace.WriteLine(result);

    【讨论】:

      【解决方案3】:

      这样会更好:

      static async Task<int> DelayAndReturnAsync(int val)
      {
          await Task.Delay(TimeSpan.FromSeconds(val));
          return val;
      }
      static async Task AwaitAndProcessAsync(Task<int> task)
      {
          var result = await task;
          Console.WriteLine(result);
      }
      // This method now prints "1", "2", and "3".
      static async Task ProcessTasksAsync()
      {
          // Create a sequence of tasks.
          Task<int> taskA = DelayAndReturnAsync(2);
          Task<int> taskB = DelayAndReturnAsync(3);
          Task<int> taskC = DelayAndReturnAsync(1);
          var tasks = new[] { taskA, taskB, taskC };
          var processingTasks = tasks.Select(AwaitAndProcessAsync).ToArray();
          // Await all processing to complete
          await Task.WhenAll(processingTasks);
      }
      

      任务数组,因为 AwaitAndProcessAsync 返回任务。

      【讨论】:

        猜你喜欢
        • 2013-06-12
        • 2014-03-09
        • 2013-06-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-24
        • 2018-01-23
        • 1970-01-01
        相关资源
        最近更新 更多