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);
        }
    }
}
Share

Leave a Reply

To comment, click below to log in.