0

I am trying to fire a method at precisely each minute.

I am using the code below, but I notice that the timer can fire several milliseconds early.

How might I fix that?

    public partial class Form1 : Form
    {

        DateTime RoundUpFuzzy(DateTime dt, TimeSpan d)
        {
            int n=1;
            if (dt.Millisecond > 900)
                n = 2;
            return new DateTime(((dt.Ticks + (d.Ticks*n) - 1) / d.Ticks) * d.Ticks);
        }

        public void HistMatch()
        {
            for (int i = 0; i < 10; i++)
            {
                var now = DateTime.UtcNow;
                var nextDT = RoundUpFuzzy(now, TimeSpan.FromMinutes(1));
                var waitTS = nextDT.Subtract(now);
                Console.WriteLine("Now: " + now.ToString("HH:mm:ss.fff") + " nextDT: " + nextDT.ToString("HH:mm:ss.fff")+" wait MS: " + waitTS.TotalMilliseconds);
                System.Threading.Thread.Sleep(waitTS);
            }
        }
}

Output:

Now: 09:12:16.308 nextDT: 09:13:00.000 waitMS: 43691.7256
Now: 09:12:59.999 nextDT: 09:14:00.000 waitMS: 60000.0952
Now: 09:13:59.990 nextDT: 09:15:00.000 waitMS: 60009.0949
Now: 09:14:59.992 nextDT: 09:16:00.000 waitMS: 60007.4955
Now: 09:15:59.993 nextDT: 09:17:00.000 waitMS: 60006.2285
Now: 09:16:59.996 nextDT: 09:18:00.000 waitMS: 60003.2285
Now: 09:17:59.996 nextDT: 09:19:00.000 waitMS: 60003.2285
Now: 09:18:59.996 nextDT: 09:20:00.000 waitMS: 60003.2285

EDIT 1 - Additional Test code

Here I am trying 3 versions to run an event exactly each minute. Only the first "Test_Sleep" is accurate. How can I get the same result but using a timer?

public partial class Form1 : Form
{
    System.Threading.Timer threadingTimer;
    System.Timers.Timer timersTimer;

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Test_Sleep();
        Test_ThreadingTimer("");
        Test_TimersTimer("", null);
    }

    public void Test_TimersTimer(object o, System.Timers.ElapsedEventArgs e)
    {
        var now = DateTime.UtcNow;
        var nextDT = RoundUpFuzzy(now, TimeSpan.FromMinutes(1));
        var ms = nextDT.Subtract(now);
        Console.WriteLine("Now: " + now.ToString("HH:mm:ss.fff") + " nextDT: " + nextDT.ToString("HH:mm:ss.fff") + " waitMS: " + ms.TotalMilliseconds);
        timersTimer = new System.Timers.Timer();
        timersTimer.Interval = (int)ms.TotalMilliseconds;
        timersTimer.Elapsed += Test_TimersTimer;
        timersTimer.AutoReset = false;
        timersTimer.Enabled = true;
    }

    public void Test_ThreadingTimer(object o)
    {
        var now = DateTime.UtcNow;
        var nextDT = RoundUpFuzzy(now, TimeSpan.FromMinutes(1));
        var ms = nextDT.Subtract(now);
        Console.WriteLine("Now: " + now.ToString("HH:mm:ss.fff") + " nextDT: " + nextDT.ToString("HH:mm:ss.fff") + " waitMS: " + ms.TotalMilliseconds);
        threadingTimer = new System.Threading.Timer(new System.Threading.TimerCallback(Test_ThreadingTimer), null, ms, TimeSpan.FromMilliseconds(-1));
    }

    public void Test_Sleep()
    {
        for (int i = 0; i < 10; i++)
        {
            var now = DateTime.UtcNow;
            var nextDT = RoundUpFuzzy(now, TimeSpan.FromMinutes(1));
            var waitTS = nextDT.Subtract(now);
            Console.WriteLine("Now: " + now.ToString("HH:mm:ss.fff") + " nextDT: " + nextDT.ToString("HH:mm:ss.fff") + " waitMS: " + waitTS.TotalMilliseconds);
            System.Threading.Thread.Sleep(waitTS);
        }
    }

    DateTime RoundUpFuzzy(DateTime dt, TimeSpan d)
    {
        int n = 1;
        if (dt.Millisecond > 900)
            n = 2;
        return new DateTime(((dt.Ticks + (d.Ticks * n) - 1) / d.Ticks) * d.Ticks);
    }
}
ManInMoon
  • 6,795
  • 15
  • 70
  • 133

2 Answers2

0

To avoid these problems, I recommend Quartz.Net (https://www.quartz-scheduler.net/). It's easy to use

Miguel Costa
  • 149
  • 8
0

Threading and Timer have a resolution of around 15 ms, so you cannot expect anything better than that...

For exact time measurement, you must use the StopWatch class from the .Net, as this is based on a high resolution frequency.

Martin Verjans
  • 4,675
  • 1
  • 21
  • 48
  • That used to be the case but is no-longer true. Certain modern Windows OS does not have that issue anymore. – ManInMoon Jun 21 '17 at 13:06
  • @ManInMoon Do you have a link that provides the info ? Because I could only find the15 ms resolution on MSDN and I would be interested in reading it. – Martin Verjans Jun 21 '17 at 13:14
  • https://stackoverflow.com/questions/3744032/why-are-net-timers-limited-to-15-ms-resolution – ManInMoon Jun 21 '17 at 13:54
  • @ManInMoon it explicitly states that certain hardware can manage it, but you need to add pieces of code to force the entire system to work with this special clock. So by using System.trheading.thread.sleep, you're still stuck at 15 ms – Martin Verjans Jun 21 '17 at 13:59
  • Martin. You are correct. In my real world app I was actually using system.threading.thread.sleep. This is pinpoint accurate. See edited question – ManInMoon Jun 22 '17 at 12:07