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:
- Fill the cache with a large set of data (approx. 1M items).
- Have the original request to the website fill the cache and not block the browers responsiveness.
- 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.