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.
#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);
}
}
}



