【问题标题】:Task.WhenAll behaves different if Task.Delay used如果使用 Task.Delay,Task.WhenAll 的行为会有所不同
【发布时间】:2021-05-11 11:31:51
【问题描述】:

我偶然发现了一些我无法真正理解的东西。

示例代码

考虑这个示例代码。

public static void Main()
{
    Example(false)
        .ConfigureAwait(false)
        .GetAwaiter()
        .GetResult();
      
    Console.ReadLine();

}
    
public static async Task Example(bool pause)
{
    List<int> items = Enumerable.Range(0, 10).ToList();

    DateTime start = DateTime.Now;

    foreach(var item in items) {
        await ProcessItem(item, pause);
    }

    DateTime end = DateTime.Now;

    Console.WriteLine("using normal foreach: " + (end - start));
    
    var tasks = items.Select(x => ProcessItem(x, pause));

    start = DateTime.Now;

    await Task.WhenAll(tasks);
    
    end = DateTime.Now;

    Console.WriteLine("using Task.WhenAll " + (end - start));
}

public static async Task ProcessItem(int item, bool pause)
{
    Console.WriteLine($"[{item}]: invoked at " + DateTime.Now.ToString("hh:mm:ss.fff tt"));

    if (pause) {
        await Task.Delay(1);
    }

    int x = 5;

    for (int i = 0; i < 1 * 1000000; i++) {
        x = await Calculate(i);
    }
   
}

public static async Task<int> Calculate(int item)
{
    return await Task.FromResult(item + 5);
}

Example 方法中,我只需调用ProcessItem 方法,首先使用普通的foreach,然后使用Task.WhenAll

ProcessItem 接受一些数字和一个标志,指示是否应该调用await.TaskDelay(1),稍后会详细介绍。除此之外,它所做的一切都是模拟一些运行时间更长的代码 + 对第三个可等待方法 (Calculate) 的调用。

结果

运行代码时的结果是

[0]: invoked at 01:19:17.417
[1]: invoked at 01:19:17.898
[2]: invoked at 01:19:18.330
[3]: invoked at 01:19:18.782
[4]: invoked at 01:19:19.118
[5]: invoked at 01:19:19.472
[6]: invoked at 01:19:19.716
[7]: invoked at 01:19:19.961
[8]: invoked at 01:19:20.179
[9]: invoked at 01:19:20.402
using normal foreach: 00:00:03.2314927

[0]: invoked at 01:19:20.639
[1]: invoked at 01:19:20.887
[2]: invoked at 01:19:21.178
[3]: invoked at 01:19:21.440
[4]: invoked at 01:19:21.670
[5]: invoked at 01:19:21.954
[6]: invoked at 01:19:22.390
[7]: invoked at 01:19:22.880
[8]: invoked at 01:19:23.218
[9]: invoked at 01:19:23.449
using Task.WhenAll 00:00:03.0749655

正常循环和Task.WhenAll 的执行时间大致相同,看起来两个版本都是按顺序工作的,因为在这两种情况下,输出之间总是存在一些延迟。

现在让我们把事情变得奇怪。 如果我将true 而不是false 传递给Example,则该方法现在会调用await.TaskDelay(1),从而导致执行结果不同。

[0]: invoked at 01:22:17.047
[1]: invoked at 01:22:17.521
[2]: invoked at 01:22:17.886
[3]: invoked at 01:22:18.337
[4]: invoked at 01:22:18.735
[5]: invoked at 01:22:19.024
[6]: invoked at 01:22:19.262
[7]: invoked at 01:22:19.500
[8]: invoked at 01:22:19.731
[9]: invoked at 01:22:19.992
using normal foreach: 00:00:03.2050316
[0]: invoked at 01:22:20.240
[1]: invoked at 01:22:20.241
[2]: invoked at 01:22:20.241
[3]: invoked at 01:22:20.241
[4]: invoked at 01:22:20.242
[5]: invoked at 01:22:20.242
[6]: invoked at 01:22:20.242
[7]: invoked at 01:22:20.243
[8]: invoked at 01:22:20.243
[9]: invoked at 01:22:20.244
using Task.WhenAll 00:00:01.4674985

如您所见,正常循环照常工作,但显然 Task.WhenAll 现在决定同时为所有项目调用 ProcessItem 方法 - 而在此之前,正在处理一个接一个的项目。

问题

为什么执行await Task.Delay(1) 会产生如此巨大的差异?

为什么第一个版本(没有调用 await Task.Delay(1))不同时为所有项目调用 ProcessItem

似乎我在这里遗漏了一些东西。我已经使用 .NET 4.5 和 .NET 4.7.2 测试了代码 - 结果相同。

【问题讨论】:

  • Task.Delay 调用是代码中唯一实际异步处理事物的地方。没有它,一切都会按顺序运行。
  • Task.FromResult 不会神奇地使 CPU 绑定操作异步
  • 为什么var tasks = items.Select(x =&gt; ProcessItem(x, pause));start = DateTime.Now; 行之前?

标签: c# .net asynchronous async-await


【解决方案1】:

当您拥有await MyAsyncMethod() 时,MyAsyncMethod 会返回一个Task,它可能处于也可能不处于 IsCompleted 状态。其余包含方法的执行取决于该状态。

  • 当任务“完成”时,继续执行同步
  • 当任务尚未完成时,包含异步方法返回一个未完成任务。每当此任务完成时,将执行包含方法的其余部分。

在您的Calculate 方法中,您将返回Task.FromResult,这是一个已完成的任务。但是,Task.Delay 直到超时才完成,因此您会立即得到一个不完整的任务。

因此,使用pause==false,您的方法会同步运行,并且在 foreach 完成时所有方法都已完成,Task.WhenAll 无需等待。

对于pause==trueProcessItem 方法返回一个不完整的任务只要延迟被击中。因此,此方法的多次调用会快速启动(您会看到 Console.WriteLine 输出及时接近),只有在延迟到期后,才会执行其余的调用 - 在 Task.WhenAll 中。

【讨论】:

  • 感谢您的澄清,明白这一点。在我的实际情况中,当调用尚未完全异步的外部事物时,我确实有某种“暂停”开关。我认为一旦改变,问题就会自行解决。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多