C# ForEach()方法中的异步lambda是如何处理的?
我们在产品中遇到了一个bug,并将其归结为以下问题。 给定一个列表并使用异步lambda调用ForEach扩展方法,输出的预期顺序是什么:C# ForEach()方法中的异步lambda是如何处理的?,c#,linq,async-await,C#,Linq,Async Await,我们在产品中遇到了一个bug,并将其归结为以下问题。 给定一个列表并使用异步lambda调用ForEach扩展方法,输出的预期顺序是什么: public static async Task Main() { var strings = new List<string> { "B", "C", "D" }; Console.WriteLine("A"); strings.ForEach(async s => { await AsyncMethod(s);
public static async Task Main()
{
var strings = new List<string> { "B", "C", "D" };
Console.WriteLine("A");
strings.ForEach(async s => { await AsyncMethod(s); } );
Console.WriteLine("E");
}
private static async Task AsyncMethod(string s)
{
await Task.Run(() => { Console.WriteLine(s); });
}
有人能解释一下区别在哪里吗?这些异步lambda是如何执行的,为什么不等待它们
澄清:问题不是B、C和D的顺序。问题是E在循环完成之前出现问题是由于没有等待列表中每个字符串上的任务完成
Console.WriteLine("A");
strings.ForEach( s => { AsyncMethod(s).Wait(); });
Console.WriteLine("E");
上述解决方案工作正常。希望有帮助
ForEach
不支持async
委托,因为它执行操作
。这会将您的方法减少到不可等待的异步void
ForEach
从未用于Func
或任何其他异步变体。在asynchmethod
上调用.Wait
将导致单线程同步上下文死锁。不过,您有一些选择:
- 标准
ForEach
wait
是每个迭代的一部分。在当前迭代完成之前,下一个迭代不会开始
由于不异步处理任务,这些顺序任务将按启动顺序完成
另一方面,这种方法的工作原理不同:
ForEach
启动任务,但不会立即等待它们。由于异步处理的性质,这些并发任务可以在每次运行代码时以不同的顺序完成
由于没有任何东西等待由ForEach
生成的任务,因此“E”任务立即启动。BCDE都是异步处理的,可以按任意顺序完成
您可以重新设计您的
foreach
示例,以匹配您的foreach
示例:
foreach (var s in strings)
{
AsyncMethod(s);
}
现在,处理与ForEach中的处理相同:
但是,如果要确保只有在BCD全部完成时才启动E任务,只需将BCD任务保存在一个集合中,等待它们在一起:
foreach (var s in strings)
{
myTaskList.Add(AsyncMethod(s));
}
await Task.WhenAll(myTaskList);
那么您可能必须添加一个continuationA continuation on what?我认为这是因为
ForEach
方法将Action
作为参数,并且Action
不是异步的。因此,每个任务都已启动但未等待。ForEach
不适用于async
方法,调用.Wait()
可能会导致死锁。您显示的第二个代码段并不像您所说的那样等待所有任务。您的第二个示例不正确,没有等待1、2、3的内容。该方法立即继续通过ForEach
,因为它返回一个async void
@Servy:我已经在纠正它了,你是对的。我太专注于解释为什么foreach
等待每一次迭代,而我忽略了foreach
中缺少等待的原因:)@JSteward:请看上面的评论。答案很好!类似的可读代码(即.ForEach
vsForEach
)有时会忽略这些缺陷。我认为这是我的关键点。您可以在ForEach中放置异步委托,但它们只是被调用而不是等待。
strings.ForEach(async s => { await AsyncMethod(s); });
foreach (var s in strings)
{
AsyncMethod(s);
}
foreach (var s in strings)
{
myTaskList.Add(AsyncMethod(s));
}
await Task.WhenAll(myTaskList);