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:
An example of calling the class:
My event handler
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
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Runtime.InteropServices;
- using System.Diagnostics;
- public class PrecisionTimer : IDisposable
- {
- //Lib API declarations
- [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
- static extern uint timeSetEvent(uint uDelay, uint uResolution, TimerCallback lpTimeProc, UIntPtr dwUser, uint fuEvent);
- [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
- static extern uint timeKillEvent(uint uTimerID);
- [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
- static extern uint timeGetTime();
- [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
- static extern uint timeBeginPeriod(uint uPeriod);
- [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
- static extern uint timeEndPeriod(uint uPeriod);
- //Timer type definitions
- [Flags]
- public enum fuEvent : uint
- {
- TIME_ONESHOT = 0, //Event occurs once, after uDelay milliseconds.
- TIME_PERIODIC = 1,
- TIME_CALLBACK_FUNCTION = 0x0000, /* callback is function */
- //TIME_CALLBACK_EVENT_SET = 0x0010, /* callback is event - use SetEvent */
- //TIME_CALLBACK_EVENT_PULSE = 0x0020 /* callback is event - use PulseEvent */
- }
- //Delegate definition for the API callback
- delegate void TimerCallback(uint uTimerID, uint uMsg, UIntPtr dwUser, UIntPtr dw1, UIntPtr dw2);
- private fuEvent f;
- private uint ms;
- //IDisposable code
- private bool disposed = false;
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- private void Dispose(bool disposing)
- {
- if (!this.disposed)
- {
- if (disposing)
- {
- Stop();
- }
- }
- disposed = true;
- }
- ~PrecisionTimer()
- {
- Dispose(false);
- }
- ///
- /// The current timer instance ID
- ///
- uint id = 0;
- ///
- /// The callback used by the the API
- ///
- TimerCallback thisCB;
- ///
- /// The timer elapsed event
- ///
- public event EventHandler Timer;
- protected virtual void OnTimer(EventArgs e)
- {
- if (Timer != null)
- Timer(this, e);
- }
- ///
- /// Initialize
- ///
- ///
- ///
- public PrecisionTimer(uint ms, bool repeat)
- {
- //Initialize the API callback
- thisCB = CBFunc;
- this.ms = ms;
- //Set the timer type flags
- f = fuEvent.TIME_CALLBACK_FUNCTION(repeat ? fuEvent.TIME_PERIODIC : fuEvent.TIME_ONESHOT);
- //Tell OS that we are about to need a precision timer.
- PrecisionTimer.timeBeginPeriod(1);
- }
- ///
- /// Stop the current timer instance
- /// VERY IMPORTANT TO CALL
- ///
- public void Stop()
- {
- lock (this)
- {
- if (id != 0)
- {
- timeKillEvent(id);
- Trace.WriteLine("Timer " + id.ToString() + " stopped " + DateTime.Now.ToString("HH:mm:ss.ffff"));
- id = 0;
- }
- }
- //Tell OS that we are done using the precision timer and that it can continue back to normal.
- PrecisionTimer.timeEndPeriod(1);
- }
- ///
- /// Start a timer instance
- ///
- /// Timer interval in milliseconds
- /// If true sets a repetitive event, otherwise sets a one-shot
- public void Start()
- {
- //Kill any existing timer
- //Stop();
- lock (this)
- {
- id = timeSetEvent(ms, 0, thisCB, UIntPtr.Zero, (uint)f);
- if (id == 0)
- throw new Exception("timeSetEvent error");
- Trace.WriteLine("Timer " + id.ToString() + " started " + DateTime.Now.ToString("HH:mm:ss.ffff"));
- }
- }
- void CBFunc(uint uTimerID, uint uMsg, UIntPtr dwUser, UIntPtr dw1, UIntPtr dw2)
- {
- //Callback from the PrecisionTimer API that fires the Timer event. Note we are in a different thread here
- OnTimer(new EventArgs());
- }
- }
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();
}