views:

139

answers:

4

I want a way to be able to schedule callbacks, I want to be able register lots of different callbacks at various times to a "Scheduler"-object. Something like this.

public class Foo
{
    public void Bar()
    {
        Scheduler s = new Scheduler();
        s.Schedule(() => Debug.WriteLine("Hello in an hour!"), DateTime.Now.AddHours(1));
        s.Schedule(() => Debug.WriteLine("Hello a week later!"), DateTime.Now.AddDays(7));
    }
}

The best way I can come up with for implementing the Scheduler is to have a timer running inside at a given interval and each time the interval has elapsed I check the registered callbacks and see if it's time to call them, if so I do. This is quite simple but has the drawback that you only get the "resolution" of the timer. Let's say the timer is set to tick once a second and you register a callback to be called in half a second it still may not be called for a whole second.

Is there a better way to solve this?

+2  A: 

This is a good approach. You should dynamically set the timer to go off as soon as the next event is going to occur. You do this by putting jobs in a priority queue. After all, in any case you are always limited to the resolution the system can provide, but you should code so that it be the only limiting factor.

Mehrdad Afshari
I swear I thought of this solution the instance I hit the send button on the question. It's good to know that it's the right approach though.
Patrik Hägne
+1  A: 

A good way to do it is to vary the duration of your timer: tell it to go off when (but not before) your first/next scheduled event is due.

<Standard disclaimer about Windows not being a real-time operating system, so its timers must always all be a little inaccurate>

ChrisW
+1  A: 

You only really need the scheduler to wake up and execute something when the earlest item on the list is due. After that you set the timer to wake up the scheduler for the next time.

When a new item is added you just compare its schedule with the current one being waited on. If its earlier you cancel the current timer and set the new item as the next scheduled item.

AnthonyWJones
A: 

Here's a class I wrote to do that, using .NET's built-in Timer class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Sample
{
    /// <summary>
    /// Use to run a cron job on a timer
    /// </summary>
    public class CronJob
    {
        private VoidHandler cronJobDelegate;
        private DateTime? start;
        private TimeSpan startTimeSpan;
        private TimeSpan Interval { get; set; }

        private Timer timer;

        /// <summary>
        /// Constructor for a cron job
        /// </summary>
        /// <param name="cronJobDelegate">The delegate that will perform the task</param>
        /// <param name="start">When the cron job will start. If null, defaults to now</param>
        /// <param name="interval">How long between each execution of the task</param>
        public CronJob(VoidHandler cronJobDelegate, DateTime? start, TimeSpan interval)
        {
            if (cronJobDelegate == null)
            {
                throw new ArgumentNullException("Cron job delegate cannot be null");
            }

            this.cronJobDelegate = cronJobDelegate;
            this.Interval = interval;

            this.start = start;
            this.startTimeSpan = DateTime.Now.Subtract(this.start ?? DateTime.Now);

            this.timer = new Timer(TimerElapsed, this, Timeout.Infinite, Timeout.Infinite);
        }

        /// <summary>
        /// Start the cron job
        /// </summary>
        public void Start()
        {
            this.timer.Change(this.startTimeSpan, this.Interval);
        }

        /// <summary>
        /// Stop the cron job
        /// </summary>
        public void Stop()
        {
            this.timer.Change(Timeout.Infinite, Timeout.Infinite);
        }

        protected static void TimerElapsed(object state)
        {
            CronJob cronJob = (CronJob) state;
            cronJob.cronJobDelegate.Invoke();
        }
    }
}
davogones
Thank's for sharing. Though it's not exactly what I'm looking for since I want to schedule multiple callbacks with one instance it's very nice to look at your code for inspiration.
Patrik Hägne
Hm... interesting idea. It probably wouldn't take too much effort to create a static Scheduler class which holds a list of registered CronJobs.
davogones