3

What .NET object (or technique) is the most precise at launching a thread every XXX milliseconds? What are the tradeoffs?

For example:

        int maxDurationMs = 1000;
        while (true)
        {
            DateTime dt = DateTime.UtcNow;
            DoQuickStuff()
            TimeSpan duration1 =  DateTime.UtcNow - dt;
            int sleepTime = maxDurationMs - duration1.Milliseconds;
            if (sleepTime > 0)
                System.Threading.Thread.Sleep(sleepTime);
        }

or

       // CPU Intensive, but fairly accurate
       int maxDurationMs = 1000;
        while (true)
        {
            DateTime dt = DateTime.UtcNow;
            DoQuickStuff()
            while (true)
            {
                if (dt.AddMilliseconds(maxDurationMs) >= DateTime.UtcNow)
                    break;
            }
        }

Alternate methods of doing the same thing, but with varying degrees of accuracy and tradeoffs (CPU, etc)

makerofthings7
  • 60,103
  • 53
  • 215
  • 448
  • A timer of some sort would do the job I'm sure; http://stackoverflow.com/questions/1416803/system-timers-timer-vs-system-threading-timer. There is no guarantee that it will fire at *exactly* N millseconds, but they will be close. – RJ Lohan May 15 '12 at 22:43
  • Precise? How precise? Unless you can put limits on what jitter is acceptable, your requirement cannot be met at all. – Martin James May 15 '12 at 23:13
  • @MartinJames Precise = within `interval * 3`, or in this case 4.5 seconds, although I'd like to do better (and think I can even with thread.sleep) – makerofthings7 May 15 '12 at 23:54
  • Related MSDN: [Comparing the Timer Classes in the .NET Framework Class Library](http://msdn.microsoft.com/en-us/magazine/cc164015.aspx) – makerofthings7 May 18 '12 at 04:36

5 Answers5

4

I have never actually used them myself, but Multimedia Timers are said to have the best resolution of any timer service in Windows. The .NET BCL does not have a wrapper for this timer service yet so you will have to do the P/Invoke calls yourself.

Another option might be to use Stopwatch together with some standard Thread.Sleep calls in a tight loop. I am not sure how much luck you would have with this approach, but it might be more accurate than a plain old Thread.Sleep call by itself. I have never tried it, but anything is worth a shot I suppose.

I did some experiments and I discovered that changing the thread priority to ThreadPriority.Highest made a considerable difference. It reduced the standard deviation of the interval by quite a bit on each technique I tried.

Brian Gideon
  • 47,849
  • 13
  • 107
  • 150
  • When using `System.Threading.Thread.Sleep()`, make sure that you use the [timeBeginPeriod](https://msdn.microsoft.com/en-us/library/windows/apps/dd757624(v=vs.85).aspx) function. Otherwise, you might wonder why you can sleep not less than 15.6ms and [how you can improve that](http://stackoverflow.com/questions/7614936/can-i-improve-the-resolution-of-thread-sleep). – Twonky Sep 09 '15 at 11:54
  • Related: http://stackoverflow.com/questions/4212611/raise-event-in-high-resolution-interval-timer - this gives hints on how to actually use these. – ivan_pozdeev May 30 '16 at 13:47
4

Don't use DateTime: its accuracy is limited to around 16ms on most systems. (See Eric Lippert's blog)

The most accurate method would be to have a dedicated thread running a while loop with a System.Diagnostics.Stopwatch object to count the time.

Even with the most precise and accurate timer in existance, raising an event exactly every x milliseconds is no simple task given the unpredictability of CPU time slices: I suggest looking into how games do their main loop (achieving a stable 30fps with lag compensation, for instance). A good example is OpenTK's GameWindow, specifically the RaiseUpdateFrame method.

Asik
  • 21,506
  • 6
  • 72
  • 131
  • Yeah, I did some experiments and using `Stopwatch` in a tight loop seemed to work the best especially when you set the thread to the highest priority. – Brian Gideon May 16 '12 at 17:25
2

If you want precise intervals Windows Timers are probably not what you need to be using, probably some sort of RTOS would be better suited for you.

From above link:

The Timer API was created to address problems with currently available timers... However, Windows timers are not as accurate as applications may require. Although Windows timer messages can be scheduled with millisecond accuracy, they seldom deliver that result because the accuracy of a Windows timer is dependent on the system clock and current activity. Because WM_TIMER messages are processed at a low priority, somewhat like WM_PAINT messages, they are often delayed while other messages are processed.

Mark Hall
  • 53,938
  • 9
  • 94
  • 111
2

In my window services periodic actions i use Monitor.Wait because it releases a Thread and allows me to perform action without worrying about next "timer tick" before i finish. With this I get +/- 1 ms precision. If everything goes right.

But if You need perfect precision that you can count on You shouldn't use .NET. Actually You shouldn't use Windows. There always is a possibility that Your process (or thread) will be postponed in execution.

Grzegorz W
  • 3,487
  • 1
  • 21
  • 21
  • 1
    +1 - I can certainly agree that the almost universal advice to 'USE A TIMER' for every task that requires any kind of interval or wait is often a really inappropriate way to go. – Martin James May 15 '12 at 23:18
  • I really doubt you will get 1 ms precision, since the thread time slice is something like 15 ms. – svick May 16 '12 at 01:22
0

The basic implementation of System.Timers.Timer object skewed approx 120 MS and caused me to skip at least one second every minute.

I am able to use the following technique to get a timer accurate within 1ms on a 1 minute interval. Shorter intervals may not be able to achieve the same accuracy (plus the overhead to DoWork() plays a part in this efficiency)

 public class SystemTimerTest
   {

    readonly System.Timers.Timer timerRecalcStatistics;
    readonly System.Diagnostics.Stopwatch stopwatchForRecalcStatistics = new System.Diagnostics.Stopwatch();


    public SystemTimerTest(TimeSpan range, DataOverwriteAction action)
    {
        int recalculateStatisticsEveryXMillseconds = 1000;

        timerRecalcStatistics = new System.Timers.Timer(recalculateStatisticsEveryXMillseconds);
        timerRecalcStatistics.AutoReset = true;
        timerRecalcStatistics.Elapsed += new System.Timers.ElapsedEventHandler(TimerRecalcStatisticsElapsed);
        timerRecalcStatistics.Interval = recalculateStatisticsEveryXMillseconds;
        timerRecalcStatistics.Enabled = true;


        this.maxRange = range;
        this.hashRunningTotalDB = new HashRunningTotalDB(action);
        this.hashesByDate = new HashesByDate(action);
        this.dataOverwriteAction = action;
    }


    private void TimerRecalcStatisticsElapsed(object source, System.Timers.ElapsedEventArgs e)
    {
        stopwatchForRecalcStatistics.Start();
        Console.WriteLine("The TimerRecalcStatisticsElapsed event was raised at {0}", e.SignalTime.ToString("o"));

         // DO WORK HERE


        stopwatchForRecalcStatistics.Stop();
        double timeBuffer  = GetInterval(IntervalTypeEnum.NearestSecond, e.SignalTime) - stopwatchForRecalcStatistics.ElapsedMilliseconds;

        if (timeBuffer > 0)
            timerRecalcStatistics.Interval = timeBuffer;
        else
            timerRecalcStatistics.Interval = 1;

        stopwatchForRecalcStatistics.Reset();         
        timerRecalcStatistics.Enabled = true;
    }
 }

I wonder if this lost 1 to 120ms per 1 second cycle means the CPU isn't as efficient as it could be with this implementation.

makerofthings7
  • 60,103
  • 53
  • 215
  • 448