Friday, October 19, 2012

ObjectCache in .NET 4


Microsoft has provided a nice abstraction around object caching in .Net 4 with the System.Runtime.Caching.ObjectCache abstract class. Now we can enjoy all of the caching goodness outside of web applications. MemoryCache is an included implementation but you can easily implement or wrap other cache providers if needed.

Object caching is handy when I have data that is used frequently but doesn't change often. 'Frequently' and 'often' are relative to a given problem and need to be evaluated accordingly. Different data may have different requirements as to how long they should stay in the cache. We communicate these requirements to the cache provider using CacheItemPolicy objects. In order to make it easier to tweak these requirements, I have implemented a custom configuration section for cache policies. Now I can define cache policies in the app.config or web.config this way:

    <caching>
        <policies>
            <add name="standardSliding" isDefault="true" 
                           type="Sliding" lifeInMinutes="10"/>
            <add name="twentyMinutesFromNow"
                           type="Absolute" lifeInMinutes="20"/>
            <add name="forever" type="Infinite"/>
        </policies>
    <caching>

and access it like this:

    ConfigurationManager.GetSection("caching")
        .Policies["standardSliding"].Policy;

My typical cache usage is something like this:

 var data = cacheProvider.Get(cacheKey);
 if(data == null) 
 {
  data = getDataFromSomeDataSource();
  cacheProvider.Set(cacheKey, data, cachePolicy); 
 }
 return data;

The problem with this code is that if multiple threads are requesting the same data then getDataFromSomeDataSource can be called multiple times. There is a cost to acquiring the data that I want to avoid so I need to make sure that if the data isn't already in cache, the first thread will request the data but any other threads will wait for that call to return. We need to synchronize the request with a lock.

    var data = cacheProvider.Get(cacheKey);
    if(data == null) 
    {
        lock(syncroot)
        {
            // has a previous thread placed the data in the cache?
            data = cacheProvider.Get(cacheKey);  
            if(data == null) 
            {
                data = getDataFromSomeDataSource();
                cacheProvider.Set(cacheKey, data, cachePolicy);
            }
        }
    }

This will work but what we really need is an easy way to do this without littering our codebase with the above logic. ObjectCache has AddOrGetExisting that could potentially do the job. I like the semantics of AddOrGetExisting:
  • return to me an object from the cache
  • if the object doesn't live in the cache, then add it
but in order to use it I must already have the object to add. This method returns null when the object is not already in the cache. This is logical since we originally had the object that was added but what about the case where the data will come from a potentially expensive method call?

To solve this issue I have implemented an extension method that overloads AddOrGetExisting and takes a Func<T> instead of an object argument.
public static T AddOrGetExisting<T>(this ObjectCache cacheProvider,
    string cacheKey, Func<T> fallbackFunction, CacheItemPolicy policy)

This implementation will return the cached value or, if not in the cache, will invoke the function, place the data in the cache then return the data to the caller. Unlike the original implementation, this extension method will not return null to the caller. Now, I can cache data with this:
    var data = _cacheProvider.AddOrGetExisting<MyData>("mydata", 
         () => _repository.GetMyData(), "standardSliding");

The source code with tests is available in my repository at GitHub and there is also a NuGet package available.

No comments:

Post a Comment