Search code examples
c#manualresetevent

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


So I am using System.Threading.ManualResetEventSlim in my code and I happened to notice that sometimes when I call Wait(TimeSpan) the time spent waiting is significantly less than the time specified.

Here is a unit test that demonstrates my situation

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));
            }
        }
    }
}

This test will pass most of the time but when repeated 100 times there is always at least 1 failure because the time measured by the stopwatch is less than the specified wait TimeSpan. A typical failure is

Expected: greater than or equal to 00:00:00.2500000 But was: 00:00:00.2497514

My first thought was that the stopwatch was not accurate enough but that appears not to be the case; the Stopwatch.Frequency = 3507511, which means that it should be accurate to something like 285ns per tick i.e. much, much less than the discrepancy of 0.25ms (assuming that it can accurately count ticks).

The fact that it waits a few fractions of a ms less than I expected does not have any impact on my particular program but I was curious and my Google-foo did not uncover anything relevant. So I put it to the SO community to see if someone has a reasonable explanation.


Solution

  • ManualResetEventSlim ultimately uses Environment.TickCount (see http://referencesource.microsoft.com/#mscorlib/system/threading/ManualResetEventSlim.cs,8a17ba6e95765ed8 and http://referencesource.microsoft.com/#mscorlib/system/threading/SpinWait.cs,9212529427afb371) . The docs state:

    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

    As such, Stopwatch is likely being more accurate / precise than ManualResetEventSlim.