Using the multimedia timer from c#
Monday, December 7, 2009 at 11:18PM In my previous project I developed software components used in large scale real time simulations. In many of the components I wrote, I needed to know a certain amount of time had passed before doing something. I needed timing information that was more accurate, a timer that was more consistent during certain loads and had greater resolution.
The timer options available in .Net do not meet these requirements. The System.Windows.Forms.Timer can only be used for window forms based applications and does not meet the requirements stated above. The System.Timers.Timer and System.Threading.Timer timers can be used without the need of a windows message pump and have better accuracy but are not consistent specially under heavy cpu loads.
The option of using the multimedia timer has greater resolution and accuracy the the timers provided by the standard .Net classes. The MultimediaTimer class provides the same interface methods as the System.Timers.Timer class.
1: using System;
2: using System.Runtime.InteropServices;
3: using System.Diagnostics;
4:
5: namespace System.Timers
6: {
7: // Summary:
8: // Represents the method that will handle the
9: // System.Timers.MultimediaTimer.Elapsed event
10: // of a System.Timers.MultimediaTimer.
11: //
12: // Parameters:
13: // sender:
14: // The source of the event.
15: //
16: // e:
17: // An System.Timers.MultimediaElapsedEventHandler object that contains
18: // the event data.
19: public delegate void MultimediaElapsedEventHandler(object sender,
20: MultimediaElapsedEventArgs e);
21:
22: // Summary:
23: // Provides data for the System.Timers.Timer.Elapsed event.
24: public class MultimediaElapsedEventArgs : EventArgs
25: {
26: // Summary:
27: // Gets the time the System.Timers.Multimedia.Elapsed event was
28: // raised.
29: //
30: // Returns:
31: // The time the System.Timers.Multimedia.Elapsed event was raised.
32: public DateTime SignalTime { get; internal set;}
33:
34: internal MultimediaElapsedEventArgs()
35: {
36: SignalTime = DateTime.Now;
37: }
38: }
39:
40: // Summary:
41: // Generates recurring events in an application.
42: public class MultimediaTimer : IDisposable
43: {
44: //Lib API declarations
45: /// <summary>
46: /// Times the set event.
47: /// </summary>
48: /// <param name="uDelay">The u delay.</param>
49: /// <param name="uResolution">The u resolution.</param>
50: /// <param name="lpTimeProc">The lp time proc.</param>
51: /// <param name="dwUser">The dw user.</param>
52: /// <param name="fuEvent">The fu event.</param>
53: /// <returns></returns>
54: [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
55: private static extern uint timeSetEvent(uint uDelay, uint uResolution,
56: TimerCallback lpTimeProc, UIntPtr dwUser, uint fuEvent);
57:
58: /// <summary>
59: /// Times the kill event.
60: /// </summary>
61: /// <param name="uTimerID">The u timer ID.</param>
62: /// <returns></returns>
63: [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
64: private static extern uint timeKillEvent(uint uTimerID);
65:
66: /// <summary>
67: /// Times the get time.
68: /// </summary>
69: /// <returns></returns>
70: [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
71: private static extern uint timeGetTime();
72:
73: /// <summary>
74: /// Times the begin period.
75: /// </summary>
76: /// <param name="uPeriod">The u period.</param>
77: /// <returns></returns>
78: [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
79: private static extern uint timeBeginPeriod(uint uPeriod);
80:
81: /// <summary>
82: /// Times the end period.
83: /// </summary>
84: /// <param name="uPeriod">The u period.</param>
85: /// <returns></returns>
86: [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
87: private static extern uint timeEndPeriod(uint uPeriod);
88:
89:
90: /// <summary>
91: ///Timer type definitions
92: /// </summary>
93: [Flags]
94: public enum fuEvent : uint
95: {
96: /// <summary>
97: /// OneHzSignalEvent occurs once, after uDelay milliseconds.
98: /// </summary>
99: TIME_ONESHOT = 0,
100: /// <summary>
101: ///
102: /// </summary>
103: TIME_PERIODIC = 1,
104: /// <summary>
105: /// callback is function
106: /// </summary>
107: TIME_CALLBACK_FUNCTION = 0x0000,
108:
109: }
110:
111: /// <summary>
112: /// Delegate definition for the API callback
113: /// </summary>
114: private delegate void TimerCallback(uint uTimerID, uint uMsg,
115: UIntPtr dwUser, UIntPtr dw1, UIntPtr dw2);
116:
117: /// <summary>
118: /// The current timer instance ID
119: /// </summary>
120: private uint id = 0;
121:
122: /// <summary>
123: /// The callback used by the the API
124: /// </summary>
125: private TimerCallback timerCallback;
126:
127:
128: /// <summary>
129: /// Initializes a new instance of the System.Timers.MultimediaTimer
130: // class, and sets all the properties to their initial values.
131: /// </summary>
132: public MultimediaTimer()
133: {
134: Interval = 100;
135: AutoReset = true;
136: Enabled = false;
137: //Initialize the API callback
138: timerCallback = CallbackFunction;
139: }
140:
141: /// <summary>
142: /// Initializes a new instance of the System.Timers.MultimediaTimer
143: /// class, and sets the
144: /// System.Timers.MultimediaTimer.Interval property to the specified
145: /// time period.
146: ///
147: /// Parameters:
148: /// interval:
149: /// The time, in milliseconds, between events.
150: ///
151: /// Exceptions:
152: /// System.ArgumentException:
153: /// The value of the interval parameter is less than or equal to
154: /// zero, or greater than System.Int32.MaxValue.
155: /// </summary>
156: /// <param name="interval">The interval.</param>
157: public MultimediaTimer(uint interval)
158: {
159: Interval = interval;
160: AutoReset = true;
161: Enabled = false;
162: //Initialize the API callback
163: timerCallback = CallbackFunction;
164: }
165:
166:
167: /// <summary>
168: /// Gets or sets a value indicating whether the
169: /// System.Timers.MultimediaTimer should raise
170: /// the System.Timers.MultimediaTimer.Elapsed event each time the
171: /// specified interval elapses
172: /// or only after the first time it elapses.
173: ///
174: /// Returns:
175: /// true if the System.Timers.MultimediaTimer should raise the
176: /// System.Timers.MultimediaTimer.Elapsed
177: /// event each time the interval elapses; false if it should raise
178: /// the System.Timers.MultimediaTimer.Elapsed
179: /// event only once, after the first time the interval elapses. The
180: /// default is true.
181: /// </summary>
182: /// <value><c>true</c> if [auto reset]; otherwise, <c>false</c>.</value>
183: public bool AutoReset { get; set; }
184:
185: /// <summary>
186: /// Gets or sets a value indicating whether the
187: /// System.Timers.MultimediaTimer should raise
188: /// the System.Timers.MultimediaTimer.Elapsed event.
189: ///
190: /// Returns:
191: /// true if the System.Timers.MultimediaTimer should raise the
192: /// System.Timers.MultimediaTimer.Elapsed
193: /// event; otherwise, false. The default is false.
194: ///
195: /// </summary>
196: /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
197: public bool Enabled { get; private set; }
198:
199: private object syncLock = new object();
200:
201: /// <summary>
202: /// Gets or sets the interval at which to raise the
203: /// System.Timers.MultimediaTimer.Elapsed event.
204: ///
205: /// Returns:
206: /// The time, in milliseconds, between raisings of the
207: /// System.Timers.MultimediaTimer.Elapsed
208: /// event. The default is 100 milliseconds.
209: ///
210: /// Exceptions:
211: /// System.ArgumentException:
212: /// The interval is less than or equal to zero.
213: /// </summary>
214: /// <value>The interval.</value>
215: public uint Interval { get; set; }
216:
217:
218: /// <summary>
219: /// Occurs when the interval elapses.
220: /// </summary>
221: public event MultimediaElapsedEventHandler Elapsed;
222:
223:
224: /// <summary>
225: /// Releases the resources used by the System.Timers.MultimediaTimer.
226: /// </summary>
227: public void Close()
228: {
229: Dispose();
230: }
231:
232:
233: /// <summary>
234: /// Starts raising the System.Timers.MultimediaTimer.Elapsed event by
235: /// setting System.Timers.MultimediaTimer.Enabled
236: /// to true.
237: ///
238: /// Exceptions:
239: /// System.ArgumentOutOfRangeException:
240: /// The System.Timers.MultimediaTimer is created with an interval
241: /// equal to or greater than
242: /// System.Int32.MaxValue + 1, or set to an interval less than zero.
243: /// </summary>
244: public void Start()
245: {
246: lock (syncLock)
247: {
248: //Kill any existing timer
249: Stop();
250: Enabled = false;
251:
252: //Set the timer type flags
253: fuEvent f = fuEvent.TIME_CALLBACK_FUNCTION | (AutoReset ?
254: fuEvent.TIME_PERIODIC : fuEvent.TIME_ONESHOT);
255:
256: id = timeSetEvent(Interval, 0, timerCallback, UIntPtr.Zero,
257: (uint)f);
258: if (id == 0)
259: {
260: throw new Exception("timeSetEvent error");
261: }
262: Debug.WriteLine("MultimediaTimer " + id.ToString() + "
263: started");
264: Enabled = true;
265: }
266: }
267:
268:
269: /// <summary>
270: /// Stops raising the System.Timers.MultimediaTimer.Elapsed event by
271: /// setting System.Timers.MultimediaTimer.Enabled
272: /// to false.
273: /// </summary>
274: public void Stop()
275: {
276: lock (syncLock)
277: {
278: if (id != 0)
279: {
280: timeKillEvent(id);
281: Debug.WriteLine("MultimediaTimer " + id.ToString() + "
282: stopped");
283: id = 0;
284: Enabled = false;
285: }
286: }
287: }
288:
289: /// <summary>
290: /// Called when [timer].
291: /// </summary>
292: protected virtual void OnTimer()
293: {
294: if (Elapsed != null)
295: {
296: Elapsed(this, new MultimediaElapsedEventArgs());
297: }
298: }
299:
300: /// <summary>
301: /// CBs the func.
302: /// </summary>
303: /// <param name="uTimerID">The u timer ID.</param>
304: /// <param name="uMsg">The u MSG.</param>
305: /// <param name="dwUser">The dw user.</param>
306: /// <param name="dw1">The DW1.</param>
307: /// <param name="dw2">The DW2.</param>
308: private void CallbackFunction(uint uTimerID, uint uMsg, UIntPtr dwUser,
309: UIntPtr dw1, UIntPtr dw2)
310: {
311: //Callback from the MultimediaTimer API that fires the Timer event.
312: // Note we are in a different thread here
313: OnTimer();
314: }
315:
316: #region IDisposable Members
317:
318: private bool _disposed = false;
319:
320:
321: /// <summary>
322: /// Performs application-defined tasks associated with freeing,
323: /// releasing, or resetting unmanaged resources.
324: /// Releases all resources used by the current
325: /// System.Timers.MultimediaTimer.
326: ///
327: /// Parameters:
328: /// disposing:
329: /// true to release both managed and unmanaged resources; false to
330: /// release only
331: /// unmanaged resources.
332: /// </summary>
333: public void Dispose()
334: {
335: Dispose(true);
336: GC.SuppressFinalize(this);
337: }
338:
339: /// <summary>
340: /// Releases unmanaged and - optionally - managed resources
341: /// </summary>
342: /// <param name="disposing"><c>true</c> to release both managed and
343: /// unmanaged resources; <c>false</c> to release only unmanaged
344: /// resources.</param>
345: private void Dispose(bool disposing)
346: {
347: if (!_disposed)
348: {
349: if (disposing)
350: {
351: Stop();
352: }
353: }
354: _disposed = true;
355: }
356:
357: /// <summary>
358: /// Releases unmanaged resources and performs other cleanup operations
359: /// before the
360: /// <see cref="MMTimer"/> is reclaimed by garbage collection.
361: /// </summary>
362: ~MultimediaTimer()
363: {
364: Dispose(false);
365: }
366:
367: #endregion
368: }
369: }
Laureano Lopez
This follow up is a bug fix update to the MultimediaTimer code. The issue is that the unmanaged Win32 API function takes a TimerCallback delegate as one of its parameters as seen here.
[DllImport("Winmm.dll", CharSet = CharSet.Auto)]
55: private static extern uint timeSetEvent(uint uDelay, uint uResolution,
56: TimerCallback lpTimeProc, UIntPtr dwUser, uint fuEvent);
Since the TimerCallback delegate is a managed .Net object, the garbage collector may move the delegate object to another memory location therefore leaving the unmanaged function timeSetEvent with an invalid delegate object. To fix the error the delegate object would have to be pinned to a fixed memory location. To fix the error I added the following line of code after the delegate object is assigned (line 163)
_gcHandle = GCHandle.Alloc(timerCallback);
Also add the following line of code to the Dispose method
_gcHandle.Free();
One more thing do not forget to declare the field
private GCHandle _gcHandle;
I want to thank Jason for pointing out my error. Thank you Jason.
Here is a reference link that elaborates on this error:
http://msdn.microsoft.com/en-us/library/367eeye0%28VS.80%29.aspx
.Net,
c#,
c# timer,
multimedia timer 
Reader Comments (1)
Hey, that was interesting,
Its fantastic i can now use the multimedia timer from c#
Anyway, thanks for the post