Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task.Delay completes earlier

I have observed in both .NET Framework and .NET Core that Task.Delay() appears to complete earlier than it should. Usually the underage is 10's usecs, but on rare occasion it can be as much as a few msecs. Consider this program:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("TaskDelayTest .NET Framework");
        while (true)
        {
            DateTime now = DateTime.UtcNow;
            TimeSpan wait = TimeSpan.FromMilliseconds(1000);
            DateTime then = now + wait;
            Task delay = Task.Delay(wait);
            delay.ContinueWith(Execute, then);
            Thread.Sleep(100);
        }
    }

    static void Execute(Task delay, object arg)
    {
        DateTime later = DateTime.UtcNow;
        DateTime then = (DateTime)arg;
        if (later < then)
        {
            Console.WriteLine("Early execute!!!!  {0:n0} ns", (then.Ticks - later.Ticks) * 100);
        }
    }
}

I would expect that the "Early execute" line is NEVER printed because Task.Delay should wait at least as long as the delay parameter. However, this is not what I observe. If you allow the program to run long enough eventually it does print out "Early execute". Have I misinterpreted the spec here?

TaskDelayTest .NET Core
Early execute!!!!  199,800 ns
Early execute!!!!  22,200 ns
Early execute!!!!  353,300 ns
Early execute!!!!  571,200 ns
Early execute!!!!  90,700 ns
Early execute!!!!  85,600 ns
Early execute!!!!  9,300 ns
Early execute!!!!  540,600 ns
Early execute!!!!  141,200 ns
Early execute!!!!  107,800 ns
Early execute!!!!  397,200 ns
Early execute!!!!  297,000 ns
like image 662
jthunter Avatar asked Dec 24 '22 11:12

jthunter


1 Answers

The variation in wake times you're seeing is well within expected performance on Windows. In a 1000 ms wait, you are seeing early wake-ups occasionally, always around 1 ms or less.

When looking at issues like this, it's useful and informative to look at the documentation for the lower-level OS features on which these higher abstractions are built. In the case of Task.Delay(), this is based on a timer thread that maintains a queue of timer events, with Thread.SleepEx() used to induce a delay between the timer events. In the documentation for that function, it reads:

If dwMilliseconds is less than the resolution of the system clock, the thread may sleep for less than the specified length of time. If dwMilliseconds is greater than one tick but less than two, the wait can be anywhere between one and two ticks, and so on.

In other words, no matter what the wait time you've specified, if it's not an even multiple of the system clock's resolution, you can get wake-ups earlier than the time you specified, within the range of the system clock resolution.

Pretty much any timing mechanism that relies on the thread scheduler is going to have the same limitation, including Task.Delay().

like image 102
Peter Duniho Avatar answered Jan 12 '23 16:01

Peter Duniho