Timer Queue
For a C# app I wrote a while back, I had need for many timer “alarm” notifications to activate arbitrary callbacks. Certainly I could have made one System.Threading.Timer instance for each alarm, but I figured that was overly resource intensive. Furthermore, I wanted the opportunity for all pending alarms to be cancelled instantly, if need be. I checked to see if the BCL already offered some kind of “timer queue,” and requested it when I found that it didn’t. In the mean-time, I wrote my own. I hope you can find use for it as well.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
#region Copyright AxisCode LLC // // This file is free for all uses, commercial or otherwise, without // royalty or payment. This copyright notice must be retained on this // version, or any derivative version of this code file. // // author: Brent Arias // filename: TimerQueue.cs // #endregion using System; using System.Collections.Generic; //using System.Linq; using System.Text; using System.Threading; using C5; namespace AxisCode { /// <summary> /// Used to launch timer callbacks, but uses only one timer internally. /// See: http://social.msdn.microsoft.com/Forums/en/netfxbcl/thread/95f9a084-5d57-41a1-8d48-644bf8910858 /// </summary> public static class Timer { //The "IntervalHeap" is a priority queue from the C5 generic collection library: //http://www.drdobbs.com/windows/199902700;jsessionid=RBSYYM1YINYNBQE1GHPSKH4ATMY32JVN?pgno=2 //A magic number used by the BCL, to indicate the timer should fire only once. static readonly TimeSpan noPolling = TimeSpan.FromMilliseconds(-1.0); static object thisLock = new object(); static System.Threading.Timer timer = new System.Threading.Timer(Dispatch); static IntervalHeap<callbackInfo> alarms = new IntervalHeap<callbackInfo>(); private static void Dispatch(object param){ callbackInfo first; bool immediate = false; lock (thisLock) { first = alarms.DeleteMin(); if (alarms.Count > 0) { callbackInfo newFirst = alarms.FindMin(); TimeSpan next = newFirst.eventTime - DateTime.Now; if (next > TimeSpan.Zero) { timer.Change(next, noPolling); } else { immediate = true; } } } //This method is already being executed from a threadpool thread. There //is little sense in making another... //ThreadPool.QueueUserWorkItem(new WaitCallback(first.callback), first.arg); //So just call the method directly... first.callback(first.arg); if (immediate) Dispatch(null); } public static DateTime AddAlarm(TimeSpan trigger, Action<object> callback, object param){ DateTime alarm; lock (thisLock) { alarm = DateTime.Now + trigger; if (alarms.Count == 0 || alarms.FindMin().eventTime > alarm) { timer.Change(trigger, noPolling); } alarms.Add(new callbackInfo(alarm, callback, param)); } return alarm; } } public struct callbackInfo : IComparable<callbackInfo> { public DateTime eventTime; public Action<object> callback; public object arg; public callbackInfo(DateTime time, Action<object> handler, object parm) { eventTime = time; callback = handler; arg = parm; } public int CompareTo(callbackInfo other) { return eventTime.CompareTo(other.eventTime); } } } |