04 November, 2008

Easy Background Tasks in ASP.NET

Disclaimer: Admittedly, I got this idea from Jeff Atwood, but I think I've improved on it quite a bit.

At work I have an ASP.NET application that needs to check whether the Department of Treasury has published a new OFAC list (a list of people with whom we can't do business). If there's a new list, we need to parse it, store it, and make sure that none of our customers have popped up on the new list.

In phase 2, we plan to move this functionality into a windows service, but for now this is how I made this all happen in the background:

First, we start with the interface for our worker objects:


public interface IAsyncWorker
{
//the name of the worker object
string Name { get; }
//return the next time the object should run
DateTime AbsoluteExpirationTime { get; }
//does the actual work the worker needs to do
void DoWork();
}

Now, all we have to do is simply:
  1. Add an implementation of our IAsyncWorker to the HttpRuntime.Cache.
  2. When the cache item expires, call the worker's DoWork() method.
  3. Add your worker item to the cache again so that it runs again.

If you follow the above steps you should end up with something like:

private static CacheItemRemovedCallback OnCacheRemoved = null;

protected void Application_Start(object sender, EventArgs e)
{
AddAsyncTask(new BlacklistWorker()); //BlacklistWorker implements IAsyncWorker, of course
}

private void AddAsyncTask(IAsyncWorker worker)
{
OnCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved);
HttpRuntime.Cache.Insert(worker.Name, worker, null,
worker.AbsoluteExpirationTime, Cache.NoSlidingExpiration,
CacheItemPriority.NotRemovable, OnCacheRemoved);
}

public void CacheItemRemoved(string workerName, object worker, CacheItemRemovedReason r)
{
IAsyncWorker asyncWorker = worker as IAsyncWorker;
if(null != asyncWorker)
{
asyncWorker.DoWork();
AddAsyncTask(asyncWorker);
}
}

I like this version better than Jeff's because it removes all conditional statements the CacheItemRemoved() method would have had if we had not created and IAsyncWorker interface.

This has been working great in our initial tests, but we still plan to move this to an external windows service at some point.

We're not worried about running out of threads (the thread does come out of the AppPool), since our task only needs to run every 24 hours. However, you might run into issues if you need your code to execute under a different identity than the threads in the AppPool.

This is a great technique: it gives you the ability to do async tasks with very little overhead.

0 comments:

Post a Comment