在LINQ语句中使用async / await时实际发生了什么?

时间:2014-04-16 13:32:53

标签: c# linq asynchronous c#-5.0

以下代码段编译,但我希望它等待任务结果,而不是给我一个List<Task<T>>

var foo = bars.Select(async bar => await Baz(bar)).ToList()

如指出here,您需要使用Task.WhenAll

var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList();
await Task.WhenAll(tasks);

a comment指出async内的awaitSelect()不需要:

var tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();

类似的问题here,有人试图在Where()内使用异步方法。

因此LINQ语句中的asyncawait是合法的语法,但是它什么都不做或者是否有某种用途?

2 个答案:

答案 0 :(得分:30)

我建议您不要将其视为&#34;在LINQ&#34;中使用async。请记住两者之间的内容:代表们。几个LINQ运算符接受委托,async可用于创建异步委托。

因此,如果您有一个返回BazAsync的异步方法Task

Task BazAsync(TBar bar);

然后这段代码会产生一系列任务:

IEnumerable<Task> tasks = bars.Select(bar => BazAsync(bar));

同样,如果您在委托中使用asyncawait,则需要创建一个返回Task的异步委托:

IEnumerable<Task> tasks = bars.Select(async bar => await BazAsync(bar));

这两个LINQ表达式在功能上是等价的。没有重要的区别。

就像常规LINQ表达式一样,IEnumerable<Task>是惰性求值的。只有像BazAsync这样的异步方法,你通常想要意外的双重评估或类似的东西。因此,当您投射到一系列任务时,立即重新确定序列通常是一个好主意。这将为源序列中的所有元素调用BazAsync,开始执行所有任务:

Task[] tasks = bars.Select(bar => BazAsync(bar)).ToArray();

当然,我们使用Select所做的就是启动每个元素的异步操作。如果您想等待所有内容完成,请使用Task.WhenAll

await Task.WhenAll(tasks);

大多数其他LINQ运算符不能与异步委托一起干净地工作。 Select非常简单:您只是为每个元素启动异步操作。

答案 1 :(得分:6)

  

是否有特定用途

不确定。使用async和等待LINQ语句,你可以例如做这样的事情:

var tasks = foos.Select( async foo =>
    {
        var intermediate =  await DoSomethingAsync( foo );
        return await DoSomethingElseAsync( intermediate );
    } ).ToList();
await Task.WhenAll(tasks);

如果没有在LINQ语句中使用async / await,您就不会在LINQ语句中等待任何内容,因此您无法处理结果,或等待其他内容。

如果没有async / await,在LINQ语句中,您只是启动任务,而不是等待它们完成。它们最终仍然会完成,但是在控件离开LINQ语句之后很久就会发生,所以你只能在WhenAll行完成之后访问它们的结果,但不能在LINQ语句中访问它们的结果