At blinkBox we cache a lot of data. Because we use a distributed cache, it's frequent that we need to get portions of the data from multiple cache nodes, and one way to potentially improve response times is to perform the fetches in parallel. Another way to improve response times is to put any data we had to look up from the database into the cache out-of-band, so the response doesn't have to wait for this to complete. To facilitate both of these things, we can use an asynchronous cache wrapper.
For the purpose of this entry, lets look at the implementation of just the Get methods on the ICache interface, which are defined as follows:
public interface ICache
{
object Get(string key);
object Get(string region, string key);
}
The async idiom requires that the async methods are named using the BeginX/EndX convention, where X is replaced with the name of the synchronous method. This means that we will have two methods named BeginGet which must both be ended by the same EndGet method. To begin the asynchronous call in BeginGet we can assign the Get method to an appropriate Func<> and call BeginInvoke on it; the corresponding EndInvoke will be called in the EndGet method.
Unfortunately in C# there is no way to call EndInvoke on an untyped delegate, which means it must be cast to the original type to end the invocation. In addition, there is no relationship between Func<TResult> and Func<T1, TResult> even though they both return the same type, so you cannot simply cast the delegate to a parameterless one that returns the correct type because it will fail with an InvalidCastException. As a result, my initial implementation of EndInvoke used a sequence of casts to check handle ending the invocation of both types of delegate that the invocation could have begun with:
public static class AsyncCache
{
public static IAsyncResult BeginGet(
this ICache cache, string key, AsyncCallback callback, object state)
{
Func<string, object> get = cache.Get;
return get.BeginInvoke(key, callback, state);
}
public static IAsyncResult BeginGet(
this ICache cache, string region, string key, AsyncCallback callback, object state)
{
Func<string, string, object> get = cache.Get;
return get.BeginInvoke(region, key, callback, state);
}
public static object EndGet(this ICache cache, IAsyncResult asyncResult)
{
var get = ((AsyncResult)asyncResult).AsyncDelegate;
var get1 = get as Func<string, object>;
if (get1 != null)
{
return get1.EndInvoke(asyncResult);
}
var get2 = get as Func<string, string, object>;
if (get2 != null)
{
return get2.EndInvoke(asyncResult);
}
throw new ArgumentException("Invalid async result.", "asyncResult");
}
}
Casting the IAsyncResult to a concrete AsyncResult to allow extracting the initial delegate is documented as being valid at MSDN, so although it looks a little hacky it really isn't:
The AsyncResult class is used in conjunction with asynchronous method calls made using delegates. The IAsyncResult returned from the delegate's BeginInvoke method can be cast to an AsyncResult. The AsyncResult has the AsyncDelegate property that holds the delegate object on which the asynchronous call was invoked.
However, I really don't like the chain of casts to determine the concrete type of the delegate. One reason is that it looks messy, particularly if there were a few different overloads of BeginGet. However, more importantly it is fragile, and introduces excessive coupling between the BeginGet and EndGet methods, meaning that the EndGet method would need to be changed if either of the BeginGet method signatures changed, or if a new overload was added.
What we need is some way to ensure that the EndGet method only has to deal with a single type of function that returns the result, irrespective of what the signature of the BeginGet method was. Using C# 3.0 we can do this by wrapping the function call in a parameterless lambda expression, which creates a closure to capture the argument values, and beginning the invocation on that (note that this could also be done wth anonymous delegates in C# 2.0):
public static class AsyncCache
{
public static IAsyncResult BeginGet(
this ICache cache, string key, AsyncCallback callback, object state)
{
Func<object> get = () => cache.Get(key);
return get.BeginInvoke(callback, state);
}
public static IAsyncResult BeginGet(
this ICache cache, string region, string key, AsyncCallback callback, object state)
{
Func<object> get = () => cache.Get(region, key);
return get.BeginInvoke(callback, state);
}
public static object EndGet(this ICache cache, IAsyncResult asyncResult)
{
var get = ((AsyncResult)asyncResult).AsyncDelegate as Func<object>;
if (get == null)
{
throw new ArgumentException("Invalid async result.", "asyncResult");
}
return get.EndInvoke(asyncResult);
}
}
This approach can be used for the other methods on the cache, the only difference is that for things like Put which do not have a return value, Func<T> would be replaced with Action.
Although this pattern was developed to handle the case when BeginX is overloaded, I think it is still a very useful pattern when it isn't. As the EndX method now only needs information about the return type of the function (which it has in its signature anyway) the coupling between the BeginX and EndX methods is reduced. This means that EndX doesn't have to change even if the parameters to the BeginX method change in future, improving the maintainability of the code.
Posted
Oct 13 2008, 12:16 AM
by
Greg Beech