ManInMoon
ManInMoon

Reputation: 7005

For timer precision - should I use period or set new timer?

I need to do something precisely evey minute.

Currently, I am using:

int delayMS = (60 - DateTime.Now.Second) * 1000;
int periodMS = 60000;

BarTimer = new System.Threading.Timer(new TimerCallback(BarEndProcess), null, delayMS, periodMS);

Is this likely to be more precise than restarting timer each minute with a minutes delay like:

int delayMS = (60 - DateTime.Now.Second) * 1000;

BarTimer = new System.Threading.Timer(new TimerCallback(BarEndProcess), null, delayMS, 0);

I want the CallBack to fire as closely as possible to the minute boundary.

Upvotes: 0

Views: 77

Answers (1)

Matthew Watson
Matthew Watson

Reputation: 109597

It looks like you will need to re-synch your timer every time it fires to prevent the elapsed time intervals from drifting.

Here's a test program I wrote to see if a timer callback would drift. This example is trying to get a callback every 10 seconds:

using System;
using System.Diagnostics;
using System.Threading;

namespace Demo
{
    static class Program
    {
        static void Main()
        {
            var timer = new Timer(test, null, 10000, 10000);
            Console.ReadLine();
            GC.KeepAlive(timer);
        }

        static void test(object obj)
        {
            if (stopwatch == null)
                stopwatch = Stopwatch.StartNew();

            Console.WriteLine(stopwatch.ElapsedMilliseconds);
        }

        static Stopwatch stopwatch;
    }
}

The results from this (run over lunch time!) are:

0
9995
19996
29997
39997
49998
59998
69999
80000
90000
100001
110002
120002
130003
140003
150004
160004
170005
180006
190007
200008
210009
220009
230010
240011
250012
260013
270014
280014
...
2800158
2810158
2820159
2830160
2840160
2850161
2860161
2870162
2880163
2890163
2900164
2910165
2920166
2930166
2940167
2950168

As you can see, the time at which the callback occurs is gradually drifting further and further away from every 10 seconds.

You can fix this by measuring absolute elapsed time and adjusting the callback time of the next tick. A Stopwatch should be suitable for measuring elapsed time, but you could also use DateTime.Now to do so.

Here's a sample program that uses a Stopwatch to recalibrate the timer. This example is trying to get a callback every 10 seconds.

using System;
using System.Diagnostics;
using System.Threading;

namespace Demo
{
    static class Program
    {
        const int PERIOD_MILLISECONDS = 10000;

        static void Main()
        {
            timer = new Timer(test, null, PERIOD_MILLISECONDS, PERIOD_MILLISECONDS);
            Console.ReadLine();
            GC.KeepAlive(timer);
        }

        static void test(object dummy)
        {
            if (stopwatch == null)
                stopwatch = Stopwatch.StartNew();

            Console.WriteLine(stopwatch.ElapsedMilliseconds);

            long newPeriod = PERIOD_MILLISECONDS - stopwatch.ElapsedMilliseconds + expectedElapsedMillseconds;
            timer.Change(newPeriod, PERIOD_MILLISECONDS);

            expectedElapsedMillseconds += PERIOD_MILLISECONDS;
        }

        static Stopwatch stopwatch;
        static long      expectedElapsedMillseconds;
        static Timer     timer;
    }
}

The output from this looks like this:

0
10010
20014
30011
40013
50009
60016
70001
80000
90001
100000
110000
120000
130001
140001
150001
160000
170001
180001
190001
200001
210001
220001
230001
240001
250001
260001
270001
280000
290000
300001
310000
320000
330001
340001
350001
360001

There was a slight wobble at the start, but note how the callback time is always within 16ms of when it should occur, and it is not drifting.

Upvotes: 1

Related Questions