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

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

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

正如这里指出的,你需要使用Task.WhenAll

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

但是评论指出不需要在Select()内部进行asyncawait

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

这里有一个类似的问题,有人试图在Where()使用异步方法。

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

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

所以,当你有一个返回Task的异步方法BazAsync

 Task BazAsync(TBar bar); 

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

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

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

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

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

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

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

当然,我们用Select完成的所有事情都是为每个元素启动一个异步操作。 如果你想等待它们全部完成,那么使用Task.WhenAll

 await Task.WhenAll(tasks); 

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

它有一定的用途吗?

当然。 使用async和await在LINQ语句中,您可以执行以下操作:

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

如果没有在LINQ语句中进行异步/等待,那么您不会在LINQ语句中等待任何内容,因此您无法处理结果或等待其他内容。

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