/// <summary>
/// Delegate to a method that returns a new instance of the object
/// if to be used if its not found in the Cache.
/// </summary>
public delegate object CacheLoaderDelegate();
/// <summary>
/// Return a typed object from Cache. This method locks the creation of a
/// new instance to provide thread safety.
/// </summary>
/// <typeparam name="T">Type of object to return.</typeparam>
/// <param name="key">The key used to reference the object.</param>
/// <param name="expirationMinutes">The interval between the time the
/// inserted object is last accessed and the time at which that object expires.
/// If this value is the equivalent of 20 minutes, the object will expire and
/// be removed from the cache 20 minutes after it was last accessed.</param>
/// <param name="loaderDelegate">Delegate to a method that returns a new
/// instance of the object if to be used if its not found in the Cache.</param>
/// <returns>Instance of T</returns>
public static T GetObject<T>(string key, int expirationMinutes, CacheLoaderDelegate loaderDelegate) {
Cache webCache = HttpRuntime.Cache;
object value = webCache[key];
// If the value is null create a new instance and add it to the cache.
if (value == null) {
// The reason for the lock and double null check is explained in this article
// http://aspalliance.com/1705_A_New_Approach_to_HttpRuntimeCache_Management.all
// It shows a coding flaw where multiple threads are checking the cache
// concurrently. Each of them discovers the object missing from cache and
// attempts to create and insert it. Because thread 1 has not inserted the
// object before thread 2 checks for it, they both believe it’s missing and both
// create it. The solution is to use a mutex that prevents multiple threads from
// simultaneously entering the section of code that populates the cache.
lock (typeof(CacheUtils)) {
value = webCache[key];
if (value == null) {
// Call loader delegate to a get a new instance.
value = loaderDelegate();
// Insert the new object in the Cache for a sliding number of minutes.
// This means if the ConfigurationManager is not accessed in that time period
// it will be removed from cache. Also see notes in the class comments.
webCache.Insert(key, value, null, Cache.NoAbsoluteExpiration,
TimeSpan.FromMinutes(expirationMinutes));
}
}
}
// Verify that value (whether from the cache or the delegate is of the requested type.
if (!(value is T)) {
string message = string.Format("The value in the cache or returned by the loader delegate does not match the requested type: {0}", typeof(T));
throw new InvalidCastException(message);
}
// Return the requested type.
return (T)value;
}
Tuesday, March 31, 2009
Generic thread-safe access to Cache
In my last post I showed how we needed to move the log4net logger to Cache and use a lock to avoid multiple loads of the config file. Well we had some other places in the code that also stored objects in Cache so I wanted to repeat the same pattern. Using a generic method and a delegate I was able to create the following function that can safely access the Cache from multiple threads:
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment