I have a situation where I need to have the ability to add/get an item from the cache asynchronously and has to be thread safe.
Questions
How can I simulate a tight loop scenario may be in a test where add/get cache will be retrieved without fail?
Are the following methods ThreadSafe?
GetAsync
AddAsync
GetOrAddAsync
I had a situation that the GetAsync didnt work , does it need locking?
Any suggestions input would be greatly appreciated.
Sample usage
[Fact]
public async Task GetOrAddAsync_WhenCacheExists_returnsCachedItem()
{
string cacheKey = "keyGetOrAdd";
string expectedCachedValue = "123";
var initialCacheItem = await cachingService.GetOrAddAsync(cacheKey, CachedValue);
var cacheItem = await cachingService.GetOrAddAsync(cacheKey, () => Task.FromResult("123"));
output.WriteLine("Cached Value is :{0}", cacheItem);
Assert.True(cacheItem != null);
Assert.Equal(cacheItem, expectedCachedValue);
}
public interface ICachingService
{
Task<CacheItem<T>> GetAsync<T>(string cacheKey);
Task AddAsync<T>(string key, T itemToCache, TimeSpan? expiration = null);
Task<T> GetOrAddAsync<T>(string key, Func<Task<T>> taskFactory, TimeSpan? expiration = null);
}
public class CachingService : ICachingService
{
private readonly IMemoryCache cache;
private readonly IOptions<MemoryCacheOptions> cacheOptions;
public CachingService(IOptions<MemoryCacheOptions> memoryCacheOptions)
{
cacheOptions = memoryCacheOptions;
cache = new MemoryCache(memoryCacheOptions);
}
public async Task<CacheItem<T>> GetAsync<T>(string cacheKey)
{
var result = await Task.FromResult((T)cache.Get(cacheKey));
return result != null
? new CacheItem<T>(result, true)
: new CacheItem<T>(default, false);
}
public async Task AddAsync<T>(string key, T itemToCache, TimeSpan? expiration = null)
{
if (expiration.HasValue)
{
await Task.Run(() =>
{
cache.Set(key, itemToCache, expiration.Value);
});
}
else
{
await Task.Run(() =>
{
cache.Set(key, itemToCache);
});
}
}
public async Task<T> GetOrAddAsync<T>(string key, Func<Task<T>> taskFactory, TimeSpan? expiration = null)
{
return await cache.GetOrCreateAsync(key, async entry => await new AsyncLazy<T>(async () =>
{
entry.SlidingExpiration = expiration ?? TimeSpan.FromSeconds(300);
return await taskFactory.Invoke();
}).Value);
}
}
}
public class AsyncLazy<T> : Lazy<Task<T>>
{
public AsyncLazy(Func<T> valueFactory) :
base(() => Task.Factory.StartNew(valueFactory))
{ }
public AsyncLazy(Func<Task<T>> taskFactory) :
base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap())
{ }
}
public class CacheItem<T>
{
public CacheItem(T value, bool hasValue)
{
Value = value;
HasValue = hasValue;
}
public bool HasValue { get; }
public T Value { get; }
}