Using the ASP.NET Cache as a Scheduler

UPDATED

By using the ASP.NET cache, it is very easy to create a simple scheduling mechanism, for example, one that fires an event every X minutes. I once wrote a simple module to do just this, here is its code. It is up to you to enhance it to your needs!

public class SchedulerModule: IHttpModule
	{
		#region Public constructor
		public SchedulerModule()
		{
			this.Enabled = true;			
			this.Interval = TimeSpan.FromMinutes(1);	//fires every minute
			this.FirstTime = true;
		}
		#endregion

		#region Public properties
		public Boolean Enabled
		{
			get;
			set;
		}

		public TimeSpan Interval
		{
			get;
			set;
		}

		public Boolean FirstTime
		{
			get;
			private set;
		}
		#endregion

		#region Public events
		public event EventHandler Tick;
		public event EventHandler Start;
		public event EventHandler Stop;
		#endregion

		#region Protected methods
		protected void StartScheduler()
		{
			CacheItemRemovedCallback callback = null;
			HttpContext context = HttpContext.Current;

			callback = delegate(String key, Object value, CacheItemRemovedReason reason)
			{
				if (reason == CacheItemRemovedReason.Expired)
				{
					if (this.Enabled == true)
					{
						this.StartRunning(callback, context);
					}

					if (this.FirstTime == true)
					{
						this.FirstTime = false;
						this.OnStart(EventArgs.Empty);
					}

					this.OnTick(EventArgs.Empty);

					if (this.Enabled == false)
					{
						this.OnStop(EventArgs.Empty);
					}
				}
			};

			this.StartRunning(callback, context);
		}

		protected void StartRunning(CacheItemRemovedCallback callback, HttpContext context)
		{
			context.Cache.Add(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), null, DateTime.Now + this.Interval, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, callback);
		}
		#endregion

		#region Protected virtual methods
		protected virtual void OnStart(EventArgs e)
		{
			EventHandler handler = this.Start;

			if (handler != null)
			{
				handler(this, EventArgs.Empty);
			}
		}

		protected virtual void OnStop(EventArgs e)
		{
			EventHandler handler = this.Stop;

			if (handler != null)
			{
				handler(this, EventArgs.Empty);
			}
		}

		protected virtual void OnTick(EventArgs e)
		{
			EventHandler handler = this.Tick;

			if (handler != null)
			{
				handler(this, EventArgs.Empty);
			}
		}
		#endregion

		#region IHttpModule Members

		public void Dispose()
		{
			this.Enabled = false;	
		}

		public void Init(HttpApplication context)
		{
			this.StartScheduler();
		}

		#endregion
	}


It exposes two properties:

  • Enabled: if the scheduler is active
  • Interval: the firing interval

As for the installation, you just have to reference it on your web.config:

    
    
      
    
    
    
      
        
      
    

There's two ways in which you can use it: by manually wiring an event, maybe on Application_Start, or by writing special methods, use one or the other, not both!

protected void Application_Start(Object sender, EventArgs e)
{
	//Note: you do not have to do this if you use the following methods!	
	//SchedulerModule schedulerModule = HttpContext.Current.ApplicationInstance.Modules.OfType<SchedulerModule>().Single();
        SchedulerModule schedulerModule = this.Modules.OfType<SchedulerModule>().Single();

	//Set the firing interval
	schedulerModule.Interval = TimeSpan.FromMinutes(5);
	
	schedulerModule.Start += this.Scheduler_Start;
	schedulerModule.Stop += this.Scheduler_Stop;
	schedulerModule.Tick += this.Scheduler_Tick;
}

protected void Scheduler_Start(Object sender, EventArgs e)
{
}

protected void Scheduler_Stop(Object sender, EventArgs e)
{
}

protected void Scheduler_Tick(Object sender, EventArgs e)
{
}

Bookmark and Share

                             

16 Comments

  • Thanks for the post!
    It seems though this would not be ideal to use for health monitoring of the website. I would think that applicaion pool might become inaccessible thus taking down the scheduled process. What do you think?

  • Alex,
    Yes, I think you are right; IMHO, the best thing to do for health monitoring is using Performance Counters or the ASP.NET health monitoring functionality. I will post something on them in the near future.
    What exactly is your scenario?
    RP

  • Is there a concern that events aren't firing as scheduled is the application isn't running?

  • Ben,

    The scheduler is started on the first web access, and will remain until Enabled is set to false, or the application is stopped.

    RP

  • Why not just use a timer?

  • Hi Ricardo, I think nothing prevents IIS to recycle or stop the application domain where the scheduler is running, so that will not work until IIS receives a new request to wake up the appdomain again.

  • You don't mention the obvious, why not use a Timer class which provides all these functionalities ?

    Also, you have a few bugs in the code, for example you are holding on to the HttpContext causing a small memory leak. And since ASP.NET can fire up multiple instance of your module you will get some pretty unpredictable results from this thing in production.

    Probably a fun experiment but never use this code in production, please.

  • Stefan:
    I don't think there's a memory leak; the HttpContext from the initial request (which will start the scheduler) will last as long as the HttpApplication, but only one instance of it. And how will the module fire twice? The IHttpModule.Init method is only called once, upon the first access.
    Of course, you can use any of the Timer classes (3 of them), or even the Windows Scheduler, this is just a proof of concept, think of it as a poor man's scheduler.

    cibrax:
    Of course, you are right. This is just a proof of concept.

  • Your code can run Tick() just once.

  • Will:
    Where are you calling this code from? Is HttpContex.Current not null? Also, what do you mean by running Tick only once?

  • When you start running StartScheduler() in the Init() of this module, it setup callback and execute StartRunning() by add a new cache to the HttpRuntime.

    context.Cache.Add(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), null, DateTime.Now + this.Interval, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, callback);

    When this cache expires you didn't add a new one into Cache in the delegate, so no cache item will expired so no more Tick will be executed anymore.

    The HttpContex.Current is not null, but HttpContext.Current.ApplicationInstance is sometimes get null and I don't know why.

  • Will:
    Notice the lines
    if (this.Enabled == true) { this.StartRunning(callback, context); }; they restart the scheduler, if it was not disabled.
    I don't know what is happening with HttpContext.Current.ApplicationInstance, but, IMO, you shouldn't have to call it many times - just once, to register the event handlers.

  • I followed your script and found it worked OK except in my Windows Azure project it would fire the callback multiple times and push duplicate entries to the database. I discovered it was because our app is running on multiple instances.

  • @Le-roy:
    Interesting... I have no experience with Azure, but it's nice to know these things!

  • I have implemented this, but the application (which is not heavily accessed) seems to stop, and then nothing happens anymore. Only when I run a random page from the website again, the application will startup run the scheduler again. Any way to keep the application alive ?

  • @smartpc:
    It shouldn't stop. Can you see (by debugging) that the callback is being called?
    One way it can stop is if the application is restarted; changing the web.config from the outside causes this, for example.

Add a Comment

As it will appear on the website

Not displayed

Your website