Fill server cache with a large data object and have the browser be responsive by using threads

Recently I was working on a problem, and after testing the solution I thought it would make a good blog post.  Here is what I was trying to solve: 

  1. Fill the cache with a large set of data (approx. 1M items).
  2. Have the original request to the website fill the cache and not block the browers responsiveness.
  3. Any new requests to the website should also not wait for the cache to get filled, and also not make the request to fill the cache also.

The other thing that you should be aware of is that you should have your control be able to work without data being available that you are trying to cache.  So, lets take a look at a solution - I put the call inside of a webmethod, but you can use a normal event to accomplish the same thing.  Here is the web page class: <>

    1 using System.Web.Services;

    2 using System.Collections.Generic;

    3 using System.Threading;

    4 using System.Runtime.Remoting.Messaging;

    5 using System.Web;

    6 

    7 public partial class _Default : System.Web.UI.Page

    8 {

    9     private static int _isCacheLoaded = 0;

   10     private delegate List<string> DelegateToCallMethod(string MyParameter); //match the calling method

   11     private static string myCacheKey;

   12     private static HttpContext _requestContext;

   13 

   14     protected void Page_Load(object sender, EventArgs e)

   15     {

   16 

   17     }

   18 

   19     private List<string> MyWorkerMethod(string Parameter1)

   20     {

   21         Thread.Sleep(120000); //sleep for 2 mins (REMOVE THIS IN REAL CODE - TEST ONLY)

   22         List<string> myList = new List<string>();

   23         myList.Add("Brett Robinson");

   24         return myList;

   25     }

   26 

   27     [WebMethod]

   28     public static List<string> DoSomething(string MyWebParameter)

   29     {

   30         myCacheKey = "myCacheKey";

   31         //need to save the state of httpcontext because it will get dereferenced in the callback

   32         _requestContext = HttpContext.Current;

   33         if (HttpContext.Current.Cache[myCacheKey] == null)

   34         {

   35             if (Interlocked.Exchange(ref _isCacheLoaded, 1) == 0)

   36             {

   37                 AsyncCallback callback = new AsyncCallback(CacheData);  //point to callback method

   38 

   39                 //custom delegate to invoke worker method

   40                 DelegateToCallMethod myCustomDelegate = new DelegateToCallMethod(MyWorkerMethod);

   41 

   42                 myCustomDelegate.BeginInvoke(MyWebParameter, callback, null); //call method

   43             }

   44         }

   45         return HttpContext.Current.Cache[myCacheKey] as List<string>;

   46     }

   47 

   48     public static void CacheData(IAsyncResult CallbackResult)

   49     {

   50         AsyncResult asyncResult = CallbackResult as AsyncResult;

   51         DelegateToCallMethod myCustomDelegateAgain = asyncResult.AsyncDelegate as DelegateToCallMethod;

   52         List<string> myValues = myCustomDelegateAgain.EndInvoke(CallbackResult);

   53         if (myValues != null && myValues.Count > 0)

   54         {

   55             double cacheDuration = 0;

   56             if (!double.TryParse(ConfigurationManager.AppSettings["SomeCacheValue"], out cacheDuration))

   57             {

   58                 cacheDuration = 10; //default to 10

   59             }

   60             _requestContext.Cache.Insert(myCacheKey, myValues, null

   61                 , DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration);

   62         }

   63          _isCacheLoaded = 0;

   64     }

   65 }

   66 

Remember that threads are overhead, and you should only use them when they are appropriate.  Also, blocking is bad, so make sure that you never block unless completely necessary.

Hope this helps!  Enjoy.