About Me

My photo
Northglenn, Colorado, United States
I'm primarily a BI Developer on the Microsoft stack. I do sometimes touch upon other Microsoft stacks ( web development, application development, and sql server development).

Friday, February 19, 2010

Precision Timer

Ran into a problem yesterday wth timing issues, this is the first time I ever needed to create a timer with extreme precision.

Note: The precision of multithreaded timers depends on the operating system, and is typically in the 10-20 milliseconds region. This class is used to generate greater precision using the P/Invoke interop and calls the Windows multimedia timer; which has a precision of 1 ms. But that increased responsiveness comes at a cost - since the system scheduler is running more often, the system spends more time scheduling tasks, context switching, etc. This can ultimately reduce overall system performance, since every clock cycle the system is processing "system stuff" is a clock cycle that isn't being spent running your application.


So here is some code I found and changed up a bit that uses the winmm.dll timesetevent:

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Runtime.InteropServices;
  6. using System.Diagnostics;
  7.  
  8. public class PrecisionTimer : IDisposable
  9. {
  10.  
  11.     //Lib API declarations
  12.     [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
  13.     static extern uint timeSetEvent(uint uDelay, uint uResolution, TimerCallback lpTimeProc, UIntPtr dwUser, uint fuEvent);
  14.  
  15.     [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
  16.     static extern uint timeKillEvent(uint uTimerID);
  17.  
  18.     [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
  19.     static extern uint timeGetTime();
  20.  
  21.     [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
  22.     static extern uint timeBeginPeriod(uint uPeriod);
  23.  
  24.     [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
  25.     static extern uint timeEndPeriod(uint uPeriod);
  26.  
  27.     //Timer type definitions
  28.     [Flags]
  29.     public enum fuEvent : uint
  30.     {
  31.         TIME_ONESHOT = 0, //Event occurs once, after uDelay milliseconds.
  32.         TIME_PERIODIC = 1,
  33.         TIME_CALLBACK_FUNCTION = 0x0000, /* callback is function */
  34.  
  35.         //TIME_CALLBACK_EVENT_SET = 0x0010, /* callback is event - use SetEvent */
  36.  
  37.         //TIME_CALLBACK_EVENT_PULSE = 0x0020 /* callback is event - use PulseEvent */
  38.     }
  39.  
  40.     //Delegate definition for the API callback
  41.     delegate void TimerCallback(uint uTimerID, uint uMsg, UIntPtr dwUser, UIntPtr dw1, UIntPtr dw2);
  42.  
  43.     private fuEvent f;
  44.     private uint ms;
  45.  
  46.     //IDisposable code
  47.     private bool disposed = false;
  48.  
  49.     public void Dispose()
  50.     {
  51.         Dispose(true);
  52.         GC.SuppressFinalize(this);
  53.     }
  54.  
  55.     private void Dispose(bool disposing)
  56.     {
  57.         if (!this.disposed)
  58.         {
  59.             if (disposing)
  60.             {
  61.                 Stop();
  62.             }
  63.         }
  64.         disposed = true;
  65.     }
  66.  
  67.     ~PrecisionTimer()
  68.     {
  69.         Dispose(false);
  70.     }
  71.  
  72.     ///
  73.     /// The current timer instance ID
  74.     ///
  75.     uint id = 0;
  76.  
  77.     ///
  78.     /// The callback used by the the API
  79.     ///
  80.     TimerCallback thisCB;
  81.  
  82.     ///
  83.     /// The timer elapsed event
  84.     ///
  85.     public event EventHandler Timer;
  86.  
  87.     protected virtual void OnTimer(EventArgs e)
  88.     {
  89.         if (Timer != null)
  90.         Timer(this, e);
  91.     }
  92.  
  93.     ///
  94.     /// Initialize
  95.     ///
  96.     ///
  97.     ///
  98.     public PrecisionTimer(uint ms, bool repeat)
  99.     {
  100.         //Initialize the API callback
  101.         thisCB = CBFunc;
  102.  
  103.         this.ms = ms;
  104.  
  105.         //Set the timer type flags
  106.         f = fuEvent.TIME_CALLBACK_FUNCTION(repeat ? fuEvent.TIME_PERIODIC : fuEvent.TIME_ONESHOT);
  107.  
  108.         //Tell OS that we are about to need a precision timer.
  109.         PrecisionTimer.timeBeginPeriod(1);
  110.     }
  111.  
  112.     ///
  113.     /// Stop the current timer instance
  114.     /// VERY IMPORTANT TO CALL
  115.     ///
  116.     public void Stop()
  117.     {
  118.         lock (this)
  119.         {
  120.             if (id != 0)
  121.             {
  122.                 timeKillEvent(id);
  123.                 Trace.WriteLine("Timer " + id.ToString() + " stopped " + DateTime.Now.ToString("HH:mm:ss.ffff"));
  124.                 id = 0;
  125.             }
  126.         }
  127.  
  128.         //Tell OS that we are done using the precision timer and that it can continue back to normal.
  129.         PrecisionTimer.timeEndPeriod(1);
  130.     }
  131.  
  132.     ///
  133.     /// Start a timer instance
  134.     ///
  135.     /// Timer interval in milliseconds
  136.     /// If true sets a repetitive event, otherwise sets a one-shot
  137.     public void Start()
  138.     {
  139.         //Kill any existing timer
  140.         //Stop();
  141.  
  142.         lock (this)
  143.         {
  144.             id = timeSetEvent(ms, 0, thisCB, UIntPtr.Zero, (uint)f);
  145.             if (id == 0)
  146.                 throw new Exception("timeSetEvent error");
  147.             Trace.WriteLine("Timer " + id.ToString() + " started " + DateTime.Now.ToString("HH:mm:ss.ffff"));
  148.         }
  149.     }
  150.  
  151.     void CBFunc(uint uTimerID, uint uMsg, UIntPtr dwUser, UIntPtr dw1, UIntPtr dw2)
  152.     {
  153.         //Callback from the PrecisionTimer API that fires the Timer event. Note we are in a different thread here
  154.         OnTimer(new EventArgs());
  155.     }
  156. }




An example of calling the class:


PrecisionTimer timer = new PrecisionTimer(8500, false); //will time for 8.5 seconds before triggering an event
timer.Timer += new EventHandler(timer_Timer);

timer.Start();
//Do Stuff or something until event


My event handler

void timer_Timer(object sender, EventArgs e){

timer.Stop();

DoStuff();

}