views:

3870

answers:

6

I know in certain circumstances, such as long running processes, it is important to lock ASP.NET cache in order to avoid subsequent requests by another user for that resource from executing the long process again instead of hitting the cache.

What is the best way in c# to implement cache locking in ASP.NET?

+1  A: 

Craig Shoemaker has made an excellent show on asp.net caching: http://polymorphicpodcast.com/shows/webperformance/

khebbie
although it's not thread-safe
Andreas Niedermair
A: 

This article from CodeGuru explains various cache locking scenarios as well as some best practices for ASP.NET cache locking:

Synchronizing Cache Access in ASP.NET

Jon Limjap
+15  A: 

Here's the basic pattern:

  • Check the cache for the value, return if its available
  • If the value is not in the cache, then implement a lock
  • Inside the lock, check the cache again, you might have been blocked
  • Perform the value look up and cache it
  • Release the lock

In code, it looks like this:

private static object ThisLock = new object();

public string GetFoo()
{

  // try to pull from cache here

  lock (ThisLock)
  {
    // cache was empty before we got the lock, check again inside the lock

    // cache is still empty, so retreive the value here

    // store the value in the cache here
  }

  // return the cached value here

}
AndrewDotHay
If the first load of the cache takes some minutes, is there still a way to access the entries already loaded?Lets say if I have GetFoo_AmazonArticlesByCategory(string categoryKey). I guess it would by something like a lock per categoryKey.
Malcolm Frexner
+2  A: 

I saw one pattern recently called Correct State Bag Access Pattern, which seemed to touch on this.

I modified it a bit to be thread-safe.

http://weblogs.asp.net/craigshoemaker/archive/2008/08/28/asp-net-caching-and-performance.aspx

private static object _listLock = new object();

public List List() {
    string cacheKey = "customers";
    List myList = Cache[cacheKey] as List;
    if(myList == null) {
        lock (_listLock) {
            if (myList == null) {
                myList = DAL.ListCustomers();
                Cache.Insert(cacheKey, mList, null, SiteConfig.CacheDuration, TimeSpan.Zero);
            }
        }
    }
    return myList;
}
Seb Nilsson
Couldn't two threads both get a true result for (myList==null)? Then, both threads make a call to DAL.ListCustomers() and insert the results into cache.
frankadelic
Added some thread-safety.
Seb Nilsson
After the lock you need to check the cache again, not the local `myList` variable
orip
+4  A: 

For completeness a full example would look something like this.

private static object ThisLock = new object();
object dataObject = Cache["globalData"];
if( dataObject == null )
{
    lock(ThisLock)
    {
        dataObject = Cache["globalData"];

        if( dataObject == null )
        {
            //Get Data from db
             GlobalObj globalObject = GlobalObj.GetData();
             Cache["globalData"] = globalObject;
        }
    }
}
return globalObject;
John Owen
if( dataObject == null ) { lock(ThisLock) { if( dataObject == null ) // of course it's still null!
Constantin
@Constantin: not really, someone might have updated the cache while you were waiting to acquire the lock()
Tudor Olariu
@John Owen - after the lock statement you have to try to get the object from the cache again!
Pavel Nikolov
-1, code is wrong (read the other comments), why don't you fix it? People might try to use your example.
orip
Have fixed the error in the code.
Robin Day
+2  A: 

Just to echo what Pavel said, I believe this is the most thread safe way of writing it

private T GetOrAddToCache<T>(string cacheKey, GenericObjectParamsDelegate<T> creator, params object[] creatorArgs) where T : class, new()
    {
        T returnValue = HttpContext.Current.Cache[cacheKey] as T;
        if (returnValue == null)
        {
            lock (this)
            {
                returnValue = HttpContext.Current.Cache[cacheKey] as T;
                if (returnValue == null)
                {
                    returnValue = creator(creatorArgs);
                    if (returnValue == null)
                    {
                        throw new Exception("Attempt to cache a null reference");
                    }
                    HttpContext.Current.Cache.Add(
                        cacheKey,
                        returnValue,
                        null,
                        System.Web.Caching.Cache.NoAbsoluteExpiration,
                        System.Web.Caching.Cache.NoSlidingExpiration,
                        CacheItemPriority.Normal,
                        null);
                }
            }
        }

        return returnValue;
    }