等待行为在循环中看起来是同步的
本文关键字:看起来 同步 循环 等待 | 更新日期: 2023-09-27 18:33:26
注意:这基本上只是说明我对async
感到困惑。也许它可以帮助其他困惑的人。
我正在循环中执行一些HttpClient
async
await
请求,我注意到这些请求似乎是按顺序完成的(它在开始下一次迭代之前等待结果(。
所以我尝试在循环中使用await Task.Delay
而不是 Web 请求进行测试。
也许我已经丢失了情节,但我会认为循环中的await
可能会导致循环的下一次迭代在等待结果时开始。相反,似乎一切都按顺序执行:
int _iterations = 1000;
int _count= 0;
Stopwatch _stopwatch = new Stopwatch();
_stopwatch.Start();
for (int i = 0; i < _iterations; i++)
{
Console.WriteLine("Elapsed: {0}. Calls count: {1}.", _stopwatch.Elapsed, ++_count);
await Task.Delay(5000);
Console.WriteLine("Calls count again: {0}", _count);
}
这导致:
Elapsed: 00:00:00.0000059. Calls count: 1.
Calls count again: 1
Elapsed: 00:00:05.0022733. Calls count: 2.
Calls count again: 2
Elapsed: 00:00:10.0100470. Calls count: 3.
Calls count again: 3
Elapsed: 00:00:15.0182479. Calls count: 4.
是的,我可以省略await
并将返回的Task
添加到某个数组并使用Task.WhenAll
.但令我惊讶的是,await
在我的循环中阻塞,并且这一切都是按顺序执行的。这正常吗?
编辑:所以总结:我在那里有点困惑。控制在await
返回到循环外部。这是有道理的,因为它模仿了同步类比,但当然不会阻塞线程。
这就是await
的重点:代码的外观和行为与普通同步代码几乎相同,只是它没有阻塞线程。如果要同时启动多个作业,然后等待它们全部完成,则必须在循环后移动await
。
类似的东西(假设带下划线的标识符是字段(:
async Task PerformIteration()
{
Console.WriteLine(
"Elapsed: {0}. Calls count: {1}.", _stopwatch.Elapsed, ++_count);
await Task.Delay(5000);
Console.WriteLine("Calls count again: {0}", _count);
}
...
var tasks = new List<Task>();
for (int i=0; i<_iterations; i++)
{
tasks.Add(PerformIteration());
}
await Task.WhenAll(tasks);
如果您想在每次迭代完成后立即执行某些操作(在您的情况下,第二个WriteLine()
(,则需要单独的方法。如果不需要,可以将Task.Delay()
的结果直接添加到 Task
的集合中。
预期的行为。但是当你说呼叫阻止时,你就错了。使用 await
时 UI 线程不会被阻止 - 它实际上是 fork-and-continue 的变体。当执行命中 await
时,您会立即返回到调用方,并在等待的调用完成后继续。
所以,从某种意义上说,你确实在等待,但你异步地这样做,因此得名。
如果要并行执行操作,请使用Task.WhenAll
和Task.WhenAny
构造,然后可以等待。例:
await Task.WhenAll(await DownloadAsync(), await DownloadAsync(), await DownloadAsync());
实际上,await Task.Delay(5000)
不会阻塞您的循环,Task.Delay(5000).Wait()
会阻塞。循环将通过编译器生成的await
延续回调在 5 秒内恢复,而最初调用此代码的方法可能会在您在此处输入 await
后立即恢复执行。我前段时间回答了一个类似的问题,你可能会发现它很有帮助。它展示了如何在没有await
的情况下对类似的代码段进行建模。