我们在产品中遇到了一个错误,并将其简化为以下问题。
给定一个列表并使用异步 lambda 调用 ForEach-Extension 方法,预期的输出顺序是什么:
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); });
}
我们预计它总是 A、B、C、D、E。但有时是 A、B、C、E、D 或 A、B、E、D、C
我们认为这两行是等价的:
strings.ForEach(async s => { await AsyncMethod(s); });
foreach (var s in strings) await AsyncMethod(s);
有人可以解释一下区别在哪里吗?这些异步 lambda 表达式是如何执行的以及为什么不等待它们?
澄清:问题不在于 B、C 和 D 的顺序。问题在于 E 在循环完成之前出现
foreach (var s in strings) await AsyncMethod(s);
您误解了这是如何工作的。这些是按顺序采取的步骤:
- 异步处理“B”。
- 等待(1)。
- 异步处理“C”。
- 等待(3)。
- 异步处理“D”。
- 等待(5)。
The await
是每次迭代的一部分。在当前迭代完成之前,下一次迭代不会开始。
由于不是异步处理任务,这些顺序任务将按照它们启动的顺序完成。
strings.ForEach(async s => { await AsyncMethod(s); });
另一方面,这的工作方式有所不同:
- 异步处理“B”。
- 异步处理“C”。
- 异步处理“D”。
The ForEach
启动任务,但不立即等待它们。由于异步处理的性质,每次运行代码时,这些并发任务可以以不同的顺序完成。
因为没有什么等待由 产生的任务ForEach
,“E”任务立即开始。 BCDE 都是异步处理的,可以按任意顺序完成。
你可以重新设计你的foreach
匹配您的示例ForEach
例子:
foreach (var s in strings)
{
AsyncMethod(s);
}
现在,处理方式与ForEach
:
- 异步处理“B”。
- 异步处理“C”。
- 异步处理“D”。
但是,如果您想确保 E 任务仅在 BCD 全部完成后才启动,您只需将 BCD 任务保存在集合中即可将它们一起等待:
foreach (var s in strings)
{
myTaskList.Add(AsyncMethod(s));
}
await Task.WhenAll(myTaskList);
- 异步处理“B”并将其任务添加到列表中。
- 异步处理“C”并将其任务添加到列表中。
- 异步处理“D”并将其任务添加到列表中。
- 等待列表中的所有任务,然后再执行其他操作。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)