超过 15 毫秒的等待 Task.Delay 等待的时间不够长

await Task.Delay of over 15 ms doesn't wait long enough

在对验证并发性的测试进行故障排除时,我发现我们的一些构建机器(VM 中的 运行)始终报告任务没有等待完整的 Task.Delay 时间间隔。为了确认,我编写了一个测试,它创建了一个直方图,显示从请求的延迟开始所花费的毫秒数。

在我的机器上,结果如您所料:

interval ms => number of items completing in that interval
--
0 - 4 ms => 13 # 13 iterations completed within 4 ms of the requested delay
5 - 9 ms => 194
10 - 14 ms => 714
15 - 19 ms => 61
20 - 24 ms => 12
25 - 29 ms => 3
40 - 44 ms => 2
45 - 49 ms => 1

但是在构建机器上,测试报告所有任务都在请求的延迟完成之前完成:

-10 - -6 ms => 999
-5 - -1 ms => 1

这是测试/直方图生成器:

[Test]
[Timeout(60_000)]
public async Task TaskDelayAccuracyCheck()
{
  var results = new List<long>();
  for (var i = 0; i<1000; ++i)
  {
    var sw = new Stopwatch();
    sw.Start();
    await Task.Delay(20);
    sw.Stop();
    results.Add((sw.ElapsedTicks - 20*10_000)/10_000);
  }
  var histo = results.GroupBy(t => t / 5).OrderBy(x => x.Key);
  foreach (var group in histo)
  {
    Console.WriteLine($"{group.Key*5} - {(group.Key+1)*5 - 1} ms => {group.Count()}");
  }
  Assert.Multiple(() =>
  {
    foreach (var group in histo)
    {
      Assert.That(group.Key, Is.GreaterThanOrEqualTo(0));
    }
  });
}

Task.Delay documentation 说(强调):

This method depends on the system clock. This means that the time delay will approximately equal the resolution of the system clock if the delay argument is less than the resolution of the system clock, which is approximately 15 milliseconds on Windows systems.

在这种情况下,我已确保延迟不会小于 上面提到的 15 毫秒,并且我使用了 Stopwatch class 以确保时间尽可能准确。另外,Stopwatch.IsHighResolution returns true,所以我应该可以相信时间。

我一直假设延迟总是至少是请求的时间,但系统时钟的分辨率可能会更长。我是否应该推断延迟将始终在(系统时钟分辨率)内或 Windows 上大约 15 毫秒?或者,是否发生了其他事情?

我认为你的测量不正确。

您假设每毫秒始终有 10,000 个滴答声。但情况可能并非总是如此。你可以看看Stopwatch.Frequency to see the ticks per second that it uses on the current system. That value is set the first time Stopwatch is used based on a call to the native Windows QueryPerformanceFrequency函数。

除以 1000 得到每毫秒的滴答数。

var ticksPerMillisecond = Stopwatch.Frequency / 1000;

但您可以更轻松地使用 ElapsedMilliseconds property, which accurately converts the ticks to milliseconds

results.Add(sw.ElapsedMilliseconds - 20);