Long running timers / timeouts

What is a good pattern to use for a set of long running timers? For example, when an action occurs (interrupt triggers a relay to close for example, and a LED stays lit indicating that happened) I then need a 1 minute timeout before doing one action and a 10 minute timeout before doing another action. It’s likely I’ll have multiple events, each possibly requiring multiple timers well beyond seconds. Anyone with thoughts on clean ways of handling that ?

@ Brett - See if the code below helps. Should convert quickly to MF.

using System;
using System.Collections.Generic;
using System.Threading;

namespace CommonLibrary
{
    // returning true will result in the task being reequeued with same delay
    public delegate bool TimeTaskDelegate(int id, Object state);

    public class TimerTaskManager
    {
        private class TaskBlock
        {
            public int ID { get; set; }
            public TimeTaskDelegate TaskDelegate {get;set;}
            public int DelayMS { get; set; }
            public DateTime expireTime { get; set; }
            public Object State { get; set; }

            public TaskBlock Next { get; set; }
        }

        private TaskBlock freeTaskBlocks = null;
        private TaskBlock pendingTaskQueue = null;

        private Queue<TaskBlock> executionReadyQueue = new Queue<TaskBlock>();
        private AutoResetEvent readyToExecuteEvent = new AutoResetEvent(false);
        private List<Thread> executeThreadList = new List<Thread>();

        private AutoResetEvent scheduleChangedEvent = new AutoResetEvent(false);

        private int numberOfTasks = -1;

        public TimerTaskManager(int numberOfThreads, int initialNumberOfTaskBlock = 5)
        {
            this.numberOfTasks = numberOfThreads;

            for (int i=0;i<initialNumberOfTaskBlock;i++)
            {
                TaskBlock newBlock = new TaskBlock();
                newBlock.Next = freeTaskBlocks;
                freeTaskBlocks = newBlock;
            }
        }

        public void Start()
        {
            for (int i = 0; i < numberOfTasks; i++)
            {
                Thread thread = new Thread(_ExecuteRun);
                thread.Start();
                executeThreadList.Add(thread);
            }
            (new Thread(_ScheduleRun)).Start();
        }

        private void PlaceInQueue(TaskBlock taskBlock)
        {
            lock (this)
            {
                taskBlock.Next = null;

                // place into queue
                bool atHeadOfQueue = false;
                if (pendingTaskQueue == null)
                {
                    pendingTaskQueue = taskBlock;
                    atHeadOfQueue = true;
                }
                else
                {
                    TaskBlock lastTaskBlock = null;
                    TaskBlock currentTaskBlock = pendingTaskQueue;
                    while (currentTaskBlock != null)
                    {
                        if (taskBlock.expireTime <= currentTaskBlock.expireTime)
                        {
                            if (lastTaskBlock == null)
                            {
                                // at head of queue
                                taskBlock.Next = currentTaskBlock;
                                pendingTaskQueue = taskBlock;
                                atHeadOfQueue = true;
                            }
                            else
                            {
                                // insert
                                taskBlock.Next = currentTaskBlock;
                                lastTaskBlock.Next = taskBlock;
                            }
                            break;
                        }
                        lastTaskBlock = currentTaskBlock;
                        currentTaskBlock = currentTaskBlock.Next;
                    }
                    if (currentTaskBlock == null)
                    {
                        //need to place at tail of queue
                        lastTaskBlock.Next = taskBlock;
                    }
                }
                if (atHeadOfQueue)
                    scheduleChangedEvent.Set();
            }
        }

        public void Schedule(int id, int milliseconds, TimeTaskDelegate task, Object state = null)
        {
            // calculate expiration time
            DateTime expires = DateTime.Now.AddMilliseconds(milliseconds);

            lock (this)
            {
                // get a free task block
                TaskBlock newTaskBlock;
                if (freeTaskBlocks == null)
                {
                    newTaskBlock = new TaskBlock();
                }
                else
                {
                    newTaskBlock = freeTaskBlocks;
                    freeTaskBlocks = newTaskBlock.Next;
                }

                // format block
                newTaskBlock.ID = id;
                newTaskBlock.DelayMS = milliseconds;
                newTaskBlock.expireTime = expires;
                newTaskBlock.TaskDelegate = task;
                newTaskBlock.State = state;

                PlaceInQueue(newTaskBlock);
            }
        }

        private void _ScheduleRun()
        {
            Thread.CurrentThread.Name = "TimerTaskManagerSchedulerThread";
            while (true)
            {
                Monitor.Enter(this);
                if (pendingTaskQueue == null)
                {
                    //Debug.Print("Waiting on empty queue");
                    Monitor.Exit(this);
                    scheduleChangedEvent.WaitOne();
                    continue;
                }

                DateTime now = DateTime.Now;
                TaskBlock expiredTaskBlock = null;

                if (pendingTaskQueue.expireTime <= now)
                {
                    //Debug.Print("Head of queue expired");
                    // it has expired
                    expiredTaskBlock = pendingTaskQueue;
                    pendingTaskQueue = expiredTaskBlock.Next;
                }

                if (expiredTaskBlock != null)
                {
                    // schedule for execution
                    executionReadyQueue.Enqueue(expiredTaskBlock);
                    readyToExecuteEvent.Set();
                }

                int sleepTimeMS = -1;
                if (pendingTaskQueue != null)
                {
                    TimeSpan elapsedTimeToNextExpire = pendingTaskQueue.expireTime - now;
                    sleepTimeMS = (int)elapsedTimeToNextExpire.TotalMilliseconds;
                }
                Monitor.Exit(this);

                if (sleepTimeMS == 0)
                    continue;

                if (sleepTimeMS > 0)
                {
                    //Debug.Print("Sleeping for " + sleepTimeMS.ToString() + "ms");
                    scheduleChangedEvent.WaitOne(sleepTimeMS, false);
                }
            }
        }

        private void _ExecuteRun()
        {
            Thread.CurrentThread.Name = "TimerTaskManagerExecutionThread";
            while (true)
            {
                // wait for event
                readyToExecuteEvent.WaitOne();

                TaskBlock expiredTaskBlock = null;
                lock (this)
                {
                    if (executionReadyQueue.Count == 0)
                        continue; // nothing to do

                    expiredTaskBlock = executionReadyQueue.Dequeue();

                    // see if we need awake another thread
                    if (executionReadyQueue.Count > 0)
                        readyToExecuteEvent.Set();
                }

                // we now have a taskblock to execute
                if (expiredTaskBlock.TaskDelegate(expiredTaskBlock.ID, expiredTaskBlock.State))
                {
                    expiredTaskBlock.expireTime = DateTime.Now.AddMilliseconds(expiredTaskBlock.DelayMS);
                    PlaceInQueue(expiredTaskBlock);
                }
                else
                {
                    // return to free queue
                    lock (this)
                    {
                        expiredTaskBlock.Next = freeTaskBlocks;
                        freeTaskBlocks = expiredTaskBlock;
                    }
                }
            }
        }
    }
}

2 Likes