为什么 ManualResetEventSlim.Wait 似乎没有阻塞整个等待时间?

Why does ManualResetEventSlim.Wait appear not to block for the full wait time?

所以我在我的代码中使用 System.Threading.ManualResetEventSlim 并且我碰巧注意到有时当我调用 Wait(TimeSpan) 时等待的时间明显少于指定的时间。

这是一个单元测试,展示了我的情况

using System;
using System.Diagnostics;
using NUnit.Framework;

namespace DB
{
    [TestFixture]
    public class ManualResetEventSlimTests
    {
        [Test]
        [Repeat(100)]
        public void TestThatWait_ShouldBlockForAtLeastAsLongAsTheWaitTimeout_IfNotSignalled()
        {
            TimeSpan waitTime = TimeSpan.FromMilliseconds(250);
            using (var waiter = new System.Threading.ManualResetEventSlim(false))
            {
                var stopwatch = System.Diagnostics.Stopwatch.StartNew();
                waiter.Wait(waitTime);
                Assert.That(stopwatch.Elapsed, Is.GreaterThanOrEqualTo(waitTime));
            }
        }
    }
}

此测试大部分时间都会通过,但当重复 100 次时,总是至少有 1 次失败,因为秒表测量的时间小于指定的等待时间跨度。典型的失败是

预期:大于或等于00:00:00.2500000 但是是:00:00:00.2497514

我的第一个想法是秒表不够准确,但事实并非如此; Stopwatch.Frequency = 3507511,这意味着它应该精确到每刻度 285ns,即比 0.25ms 的差异小得多(假设它可以准确计算刻度)。

它等待的时间比我预期的少几分之一毫秒这一事实对我的特定程序没有任何影响,但我很好奇并且我的 Google-foo 没有发现任何相关的东西。所以放到SO社区看看有没有人有合理的解释。

ManualResetEventSlim 最终使用 Environment.TickCount (see http://referencesource.microsoft.com/#mscorlib/system/threading/ManualResetEventSlim.cs,8a17ba6e95765ed8 and http://referencesource.microsoft.com/#mscorlib/system/threading/SpinWait.cs,9212529427afb371) 。文档状态:

Note that, because it is derived from the system timer, the resolution of the TickCount property is limited to the resolution of the system timer, which is typically in the range of 10 to 16 milliseconds

因此,Stopwatch 可能比 ManualResetEventSlim 更准确/精确。