Search code examples
c#timercallbacktimespan.net-4.7.2

The Timer with TimerCallback and TimeSpan parameter is not calling the callback cyclically


Knowing well that Windows is not a real time OS I need to call a function cyclically in a very short time-span below 1 millisecond (Requirement). To avoid a higher CPU load, I felt good about using a System.Threading.Timer and a TimerCallback instead of using Thread.Sleep in an infinitiy-loop. This actually works fine in case the cycle-time is 1ms and above. However, going just 1 microsecond below leads to the fact that the TimerCallback is called only once.

I have here an example Code where this phenomena is reproducible.

Working example - cyclically with time-span of 1ms ~ 10000 ticks. Setting a breakpoint at the TimerCallBack function one can see that it is called cyclically.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Timer timer = new Timer( new TimerCallback(TimerCallBack), null,  TimeSpan.Zero, new TimeSpan(10000));
            Console.Read();
            timer.Dispose();
        }

        public static void TimerCallBack(object o)
        {
            Console.Write(".");
        }
    }
}

In contrast the following issue-example. Setting the breakpoint at the TimerCallBack function it is clear to see, that it is called only once. But why?

Issue example - time-span of 9999 ticks and callback is for whatever reason only called once.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Timer timer = new Timer( new TimerCallback(TimerCallBack), null,  TimeSpan.Zero, new TimeSpan(9999));
            Console.Read();
            timer.Dispose();
        }

        public static void TimerCallBack(object o)
        {
            Console.Write(".");
        }
    }
}

How does it come to this situation with a value of 9999 so that the callback is not called cyclically anymore?

Is there a better solution or workaround?

.NET Framework 4.7.2


Solution

  • I was able to reproduce your observations. Here is the source code (simplified) of the System.Threading.Timer constructor that has two TimeSpan parameters:

    public Timer(TimerCallback callback,
        object? state,
        TimeSpan dueTime,
        TimeSpan period)
    {
        long dueTm = (long)dueTime.TotalMilliseconds;
        long periodTm = (long)period.TotalMilliseconds;
        TimerSetup(callback, state, (uint)dueTm, (uint)periodTm);
    }
    

    As you can see, only the milliseconds of the TimeSpans are used. Any finer detail is rounded down to the nearest integer millisecond, which in your case is zero. And according to the documentation:

    If period is zero (0) or negative one (-1) milliseconds and dueTime is positive, callback is invoked once; the periodic behavior of the timer is disabled, but can be re-enabled using the Change method.

    You could consider clicking the "Edit this document" button near the top of the documentation page (it has the icon of a pencil), add a few words to clarify this behavior, and submit a PR to the dotnet/dotnet-api-docs repository.