The Singleton pattern can help us out quite a bit. My
favorite discussion of the Singleton pattern is here: http://www.yoda.arachsys.com/csharp/singleton.html.
Note the first version is very similar to our example. Although it is marked as
bad code by the author J one thing
to consider about this article is that the variables that are being saved into
have a global scope (as in, can be accessed in some way globally). Our
variables are not static, but privately scoped to the calling method. So, let
us review the second version in our example.
Listing 2
private static readonly object locker = new object();
...
string key = "myCustomObjKey";
// attempt to retrieve the data from the cache
CustomObj customObj = HttpRuntime.Cache[ key ] as CustomObj;
// now check the local variable
if ( customObj == null )
{
// lock access here
lock ( locker )
{
// check one more time
customObj = HttpRuntime.Cache[ key ] as CustomObj;
// now check the local variable
if ( customObj == null )
{
// the object was null. We need to repopulate it
customObj = GetCustomObj();
// place it in the cache
HttpRuntime.Cache.Insert(
key
, customObj
, null
, DateTime.Now.AddMinutes( 10 )
, Cache.NoSlidingExpiration
);
}
}
}
// now it is assumed to be set. We return it to the caller
return customObj;
Here, we lock on a private static readonly object. Now, any
time there is no data to be found in the cache, there will only be a single
request that will be able to repopulate the cache. Every time this is the case,
every user requesting this data will wait on the "lock" except the
first request. As soon as the lock is released, every user will benefit from
the newly populated data. So, the question that should be raised here is how to generalize this pattern so that it is easy to accomplish?
Let consider another scenario as well.
In a typical website, there could be many different snippets
of code just like this, retrieving many diverse kinds of data from various
sources. It is also likely that in some applications, data needs to stay in
cache or just needs to be "fresh" after a certain time. This
automatic "refresh" would typically need to wait until the data
expired from cache. So, how should we generalize this pattern for any case?
What about strongly typed access as well? Wouldn't that be nice?
New Caching Pattern
Let me introduce the signature for TCache<T>.Get:
Listing 3
public class TCache<T>
{
/// <summary>
/// For safety populating and retrieving data from the HttpRuntime Cache
/// </summary>
/// <param name="key">The cache key</param>
/// <param name="refreshIntervalInSeconds">How long to retain in cache</param>
/// <param name="loaderDelegate">How to load the cache</param>
/// <returns>The object data requested</returns>
public static T Get(string key, int refreshIntervalInSeconds
, TCache.CacheLoaderDelegate loaderDelegate)
{ . . . }
}
Now, let us reveal the new Cache pattern with this new method.
Listing 4
CustomObject obj = TCache<CustomObject>.Get(
"myCustomObjKey" // cache key we are using
, 5 // number of seconds to keep in the cache
, delegate() // this is the callback that populates the cache
{
return new CustomObject();
});
TCache<T>Get consists of the pattern (simplified for
article):
object o = TCache.Get( key );
if ( IsObjectNotT<T>( o ) )
{
lock ( locker )
{
o = TCache.Get( key );
if ( IsObjectNotT<T>( o ) )
o = TCache.InternalCallback( key );
}
}
if ( IsObjectT<T>( o ) ) return (T)o;
return default(T);
The InternalCallback method manages the delegate that was
passed into TCache<T>.Get() from above and inserts the item into the
cache.