title: Combining a Stopwatch and Timer to get long-term accurate events with smooth coercion
body: <p>First, let me say that I understand that Windows is not a real-time OS.  I do not expect any given timer interval to be accurate to better than 15 ms.  However, it is important to me that the sum of my timer ticks be accurate in the long term, which is not possible with the stock timers or the stopwatch.  </p>
  
  <p>If a normal System.Timers.Timer (or, worse yet, a Windows.Forms.Timer) is configured to run every 100 ms, it ticks at somewhere between 100 ms and about 115 ms.  This means that if I need to do the event for one day, 10 * 60 * 60 * 24 = 864,000 times, I might end up finishing over three hours late!  This is not acceptable for my application.</p>
  
  <p>How can I best construct a timer for which the average duration is accurate?  I have built one, but I think it could be smoother, more effective, or...there just has to be a better way.  Why would .NET provide a low-accuracy DateTime.Now, a low-accuracy Timer, and a high-accuracy Diagnostics.Stopwatch, but no high-accuracy timer?</p>
  
  <p>I wrote the following timer update routine:</p>
  
  <pre><code>var CorrectiveStopwatch = new System.Diagnostics.Stopwatch();
  var CorrectedTimer = new System.Timers.Timer()
  {
      Interval = targetInterval,
      AutoReset = true,
  };
  CorrectedTimer.Elapsed += (o, e) =&gt;
  {
      var actualMilliseconds = ; 
  
      // Adjust the next tick so that it's accurate
      // EG: Stopwatch says we're at 2015 ms, we should be at 2000 ms
      // 2000 + 100 - 2015 = 85 and should trigger at the right time
      var newInterval = StopwatchCorrectedElapsedMilliseconds + 
                        targetInterval -
                         CorrectiveStopwatch.ElapsedMilliseconds;
  
      // If we're over 1 target interval too slow, trigger ASAP!
      if (newInterval &lt;= 0)
      {
          newInterval = 1; 
      }
  
      CorrectedTimer.Interval = newInterval;
  
      StopwatchCorrectedElapsedMilliseconds += targetInterval;
  };
  </code></pre>
  
  <p>As you can see, it's not likely to be very smooth.  And, in testing, it isn't very smooth at all!  I get the following values for one example run: </p>
  
  <pre><code>100
  87
  91
  103
  88
  94
  102
  93
  103
  88
  </code></pre>
  
  <p>This sequence has an average value of I understand that network time and other tools smoothly coerce the current time to get an accurate timer. Perhaps doing some sort of PID loop to get the target interval to targetInterval - 15 / 2 or so could require smaller updates, or perhaps smoothing could reduce the difference in timing between short events and long events.  How can I integrate this with my approach?  Are there existing libraries which do this?  </p>
  
  <p>I compared it to a regular stopwatch, and it seems to be accurate. After 5 minutes, I measured the following millisecond counter values: </p>
  
  <pre><code>DateTime elapsed:  300119
  Stopwatch elapsed: 300131
  Timer sum:         274800
  Corrected sum:     300200
  </code></pre>
  
  <p>and after 30 minutes, I measured:</p>
  
  <pre><code>DateTime elapsed:  1800208.2664
  Stopwatch elapsed: 1800217
  Timer sum:         1648500
  Corrected sum:     1800300
  </code></pre>
  
  <p>The stock timers are slower than the stopwatch by 151.8 seconds, or 8.4% of the total. That seems remarkably close to an average tick duration being between 100 and 115 ms!  Also, the corrected sum is still within 100 ms of the measured values (and these are more accurate than I can find error with my watch), so this works.</p>
  
  <p>My full test program follows.  It runs in Windows Forms and requires a text box on "Form1", or delete the <code>textBox1.Text =</code> line and run it in a command-line project.</p>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Data;
  using System.Drawing;
  using System.Linq;
  using System.Text;
  using System.Threading.Tasks;
  using System.Windows.Forms;
  
  namespace StopwatchTimerTest
  {
      public partial class Form1 : Form
      {
          private System.Diagnostics.Stopwatch ElapsedTimeStopwatch;
          private double TimerSumElapsedMilliseconds;
          private double StopwatchCorrectedElapsedMilliseconds;
          private DateTime StartTime;
  
          public Form1()
          {
              InitializeComponent();
              Run();
          }
  
          private void Run()
          {
              var targetInterval = 100;
              // A stopwatch running in normal mode provides the correct elapsed time to the best abilities of the system
              // (save, for example, a network time server or GPS peripheral)
              ElapsedTimeStopwatch = new System.Diagnostics.Stopwatch();
  
              // A normal System.Timers.Timer ticks somewhere between 100 ms and 115ms, quickly becoming inaccurate.
              var NormalTimer = new System.Timers.Timer()
              {
                  Interval = targetInterval,
                  AutoReset = true,
              };
              NormalTimer.Elapsed += (o, e) =&gt;
              {
                  TimerSumElapsedMilliseconds += NormalTimer.Interval;
              };
  
              // This is my "quick and dirty" corrective stopwatch
              var CorrectiveStopwatch = new System.Diagnostics.Stopwatch();
              var CorrectedTimer = new System.Timers.Timer()
              {
                  Interval = targetInterval,
                  AutoReset = true,
              };
              CorrectedTimer.Elapsed += (o, e) =&gt;
              {
                  var actualMilliseconds = CorrectiveStopwatch.ElapsedMilliseconds; // Drop this into a variable to avoid confusion in the debugger
  
                  // Adjust the next tick so that it's accurate
                  // EG: Stopwatch says we're at 2015 ms, we should be at 2000 ms, 2000 + 100 - 2015 = 85 and should trigger at the right time
                  var newInterval = StopwatchCorrectedElapsedMilliseconds + targetInterval - actualMilliseconds;
  
                  if (newInterval &lt;= 0)
                  {
                      newInterval = 1; // Basically trigger instantly in an attempt to catch up; could be smoother...
                  }
                  CorrectedTimer.Interval = newInterval;
  
                  // Note: could be more accurate with actual interval, but actual app uses sum of tick events for many things
                  StopwatchCorrectedElapsedMilliseconds += targetInterval;
              };
  
              var updateTimer = new System.Windows.Forms.Timer()
              {
                  Interval = 1000,
                  Enabled = true,
              };
              updateTimer.Tick += (o, e) =&gt;
              {
                  var result = "DateTime elapsed:  " + (DateTime.Now - StartTime).TotalMilliseconds.ToString() + Environment.NewLine + 
                               "Stopwatch elapsed: " + ElapsedTimeStopwatch.ElapsedMilliseconds.ToString() + Environment.NewLine +
                               "Timer sum:         " + TimerSumElapsedMilliseconds.ToString() + Environment.NewLine +
                               "Corrected sum:     " + StopwatchCorrectedElapsedMilliseconds.ToString();
                  Console.WriteLine(result + Environment.NewLine);
                  textBox1.Text = result;
              };
  
              // Start everything simultaneously
              StartTime = DateTime.Now;
              ElapsedTimeStopwatch.Start();
              updateTimer.Start();
              NormalTimer.Start();
              CorrectedTimer.Start();
              CorrectiveStopwatch.Start();
          }
      }
  }
  </code></pre>