等待行为在循环中看起来是同步的

本文关键字:看起来 同步 循环 等待 | 更新日期: 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.WhenAllTask.WhenAny构造,然后可以等待。例:

await Task.WhenAll(await DownloadAsync(), await DownloadAsync(), await DownloadAsync());

实际上,await Task.Delay(5000)不会阻塞您的循环,Task.Delay(5000).Wait()会阻塞。循环将通过编译器生成的await延续回调在 5 秒内恢复,而最初调用此代码的方法可能会在您在此处输入 await 后立即恢复执行。我前段时间回答了一个类似的问题,你可能会发现它很有帮助。它展示了如何在没有await的情况下对类似的代码段进行建模。