I'm looking into a way to wrap a service that calls a remote WCF service for some data. For example, such service may look like this
public interface IAsyncSvc
{
Task<int[]> GetData(int key);
}
public class SomeAsyncSvc:IAsyncSvc
{
public Task<int[]> GetData(int key)
{
Console.WriteLine("SomeAsyncSvc::GetData()");
return Task.Factory.StartNew(() =>
{
//Some time-consuming operation
Thread.Sleep(1000);
return Enumerable.Range(1, key).ToArray();
});
}
}
For the first time, I wrote a simple caching wrapper:
public class SomeAsyncSvcCachedTask : IAsyncSvcCached
{
private readonly IAsyncSvc _svc;
private readonly IMemoryCache _cache;
public SomeAsyncSvcCachedTask(IAsyncSvc svc, IMemoryCache cache)
{
_svc = svc;
_cache = cache;
}
public Task<int[]> GetData(int v)
{
if (_cache.TryGetValue(v, out Task<int[]> cacheEntry))
return cacheEntry;
var task = _svc.GetData(v);
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromSeconds(5));
_cache.Set(v, task, cacheEntryOptions);
return task;
}
}
The main disadvatage of caching Task<> is that if a first attempt to cache failed, I would cache a faulted task and receive the same exception over and over again querying Task.Result until another non-cached successful call.
Then I wrote another wrapper for it:
public class SomeAsyncSvcCached : IAsyncSvcCached
{
private readonly IAsyncSvc _svc;
private readonly IMemoryCache _cache;
public SomeAsyncSvcCached(IAsyncSvc svc, IMemoryCache cache)
{
_svc = svc;
_cache = cache;
}
public Task<int[]> GetData(int v)
{
if (_cache.TryGetValue(v, out int[] cacheEntry))
return Task.FromResult(cacheEntry);
var task = _svc.GetData(v);
task.ContinueWith(t =>
{
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromSeconds(5));
_cache.Set(v, t.Result, cacheEntryOptions);
}, TaskContinuationOptions.NotOnFaulted);
return task;
}
}
The main idea is not to cache Task<int[]>
, but only a result of it of type int[]
. As an advantage, if a first call failed, then this wrapper would try to read data over and over again instead of returning cached faults.
What are the flaws of such approach? Maybe there is a simpler way to achieve goal of caching method calls which return Task<>
?
Take a look at the article: http://cpratt.co/thread-safe-strongly-typed-memory-caching-c-sharp/