Caching the Result of Arbitrary Function Calls

6 August 2009
 

Here is the approach I took to performing the result of a method call, which is done in a reusable way which may be interesting.

Instead of calling my method normally like this:

var service = new MyService();
var time = service.GetTime();

I wrap that call in a call to the cache:

var service = new MyService();
var time = cache.Get(x => x.GetTime(), service, tenSeconds);

Inside the Cache implementation I have a method Get, which wraps up the method call in a wrapping function, then passes this parcel to the InvokeOrGetFromCache method:

There is a variant of “Get” for every variant of Func<>.

The InvokeOrGetFromCache is the meat of the cache class.  It controls access two the actual store of objects (a dictionary of function to result), and a collection of pending functions (a hashset).

When requesting a result for a function from the cache, the pending set is first checked.  If the job is already pending, the requesting thread yields until the job is done.

When a function’s result is not present in the cache, it is added to the pending functions set.  The function is invoked, its result is added to the cache, then the function is removed from the pending set.

NB As you can see, the key to the cache is just the function itself – this means that calls with different instances/values will result in the same result.  This is fine for my purposes, but may not be OK for you.  In this case it would be fairly straightforward to build a new key which incorporated the values as well as the function itself.

John McDowall

public class Cache
{
    private readonly IDictionary<object, CachedItem> _registry = new Dictionary<object, CachedItem>();
    private readonly ICollection<object> _pending = new HashSet<object>();

    public TResult Get<T, TResult>(Func<T, TResult> func, T t, TimeSpan maximumAge)
    {
        return InvokeOrGetFromCache(func, maximumAge, () => func.Invoke(t));
    }

    public TResult Get<T1, T2, TResult>(Func<T1, T2, TResult> func, T1 t1, T2 t2, TimeSpan maximumAge)
    {
        return InvokeOrGetFromCache(func, maximumAge, () => func.Invoke(t1, t2));
    }

    public TResult Get<T1, T2, T3, TResult>(Func<T1, T2, T3, TResult> func, T1 t1, T2 t2, T3 t3, TimeSpan maximumAge)
    {
        return InvokeOrGetFromCache(func, maximumAge, () => func.Invoke(t1, t2, t3));
    }

    public TResult Get<T1, T2, T3, T4, TResult>(Func<T1, T2, T3, T4, TResult> func, T1 t1, T2 t2, T3 t3, T4 t4, TimeSpan maximumAge)
    {
        return InvokeOrGetFromCache(func, maximumAge, () => func.Invoke(t1, t2, t3, t4));
    }

    private TResult InvokeOrGetFromCache<TResult>(object cacheKey, TimeSpan maximumAge, Func<TResult> wrapperFunc)
    {
        var now = DateTime.Now;

        WaitForPendingInvokeToComplete(cacheKey);

        lock(_registry)
        {
            if (_registry.ContainsKey(cacheKey))
            {
                var result = _registry[cacheKey];
                if (result.TimeOfConstruction.Add(maximumAge) > now)
                {
                    return (TResult) result.Value;
                }
            }
        }

        return Invoke(cacheKey, now, wrapperFunc);
    }

    private TResult Invoke<TResult>(object cacheKey, DateTime now, Func<TResult> wrapperFunc)
    {
        _pending.Add(cacheKey);
        var value = wrapperFunc.Invoke();
        _registry[cacheKey] = new CachedItem(now, value);
        _pending.Remove(cacheKey);
        return value;
    }

    private void WaitForPendingInvokeToComplete(object cacheKey)
    {
        while (_pending.Contains(cacheKey))
        {
            Thread.Sleep(0);
        }
    }

    private class CachedItem
    {
        public CachedItem(DateTime timeOfConstruction, object value)
        {
            TimeOfConstruction = timeOfConstruction;
            Value = value;
        }

        public DateTime TimeOfConstruction { get; private set; }
        public object Value { get; private set; }
    }
}

Search

Categories

Archives

Subscribe to Email Updates

Subscribe
 

We are a digital transformation consultancy. We help our clients succeed.

View Services