【问题标题】:await Task.Delay of over 15 ms doesn't wait long enough等待超过 15 毫秒的 Task.Delay 等待时间不够长
【发布时间】:2020-04-06 03:47:47
【问题描述】:

在对验证并发性的测试进行故障排除时,我发现我们的一些构建机器(在 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 说(强调):

此方法取决于系统时钟。这意味着如果延迟参数小于系统时钟的分辨率(在 Windows 系统上约为 15 毫秒),则时间延迟将近似等于系统时钟的分辨率。

在这种情况下,我确保延迟不小于上面提到的 15 毫秒,并且我使用了Stopwatch 类来确保时间尽可能精确.另外,Stopwatch.IsHighResolution 返回true,所以我应该可以相信时间安排。

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

【问题讨论】:

  • 构建服务器和您的本地机器之间可能存在环境差异吗?你说“一些”构建机器报告了这些奇怪的结果——我猜你们的一些构建机器报告了感知正确的结果?
  • 如果您需要一个准确的“计时器”,您应该始终使用新线程,如果延迟很小(
  • 我相信您涉及 ElapsedTicks 的计算不正确。你能解释一下这个计算是从哪里来的吗?使用秒表的 Elapsed.TotalMilliseconds 会更容易。
  • Stopwatch.Frequency 在同一台机器上返回什么?如果不是10000000,那么正如其他人所说,你的计算是不正确的。
  • 该代码假设一个滴答是 100 纳秒,就像 DateTime 一样。它不是。请改用 Elapsed.Ticks。

标签: c# async-await clock system-clock


【解决方案1】:

我认为你的测量不正确。

您假设每毫秒总是有 10,000 个滴答声。但情况可能并非总是如此。您可以查看Stopwatch.Frequency 以查看它在当前系统上使用的每 的滴答声。该值是 set the first time Stopwatch is used,基于对本机 Windows QueryPerformanceFrequency 函数的调用。

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

var ticksPerMillisecond = Stopwatch.Frequency / 1000;

但您可以更轻松地使用 ElapsedMilliseconds 属性,它准确地为您提供 converts the ticks to milliseconds

results.Add(sw.ElapsedMilliseconds - 20);

【讨论】:

    猜你喜欢
    • 2021-02-26
    • 2012-02-07
    • 1970-01-01
    • 2017-03-04
    • 1970-01-01
    • 1970-01-01
    • 2019-10-27
    • 2021-10-11
    • 2015-01-04
    相关资源
    最近更新 更多