0

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

  1. How can I simulate a tight loop scenario may be in a test where add/get cache will be retrieved without fail?

  2. Are the following methods ThreadSafe?

  3. GetAsync

  4. AddAsync

  5. 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; }
}
developer9969
  • 4,628
  • 6
  • 40
  • 88
  • Note that Task.Run( cache.Set) doesn't make much sense (AsyncLazy too) and actually if you only target memory cache, without other cache providers - nothing async is needed at all. Except probably the method which uses cache.GetOrCreateAsync. – Evk Nov 06 '20 at 13:57
  • @Evk I had that feeling but I was not sure.. so what do you do if you have methods that are async and within this methods you need to cache/retrieve something , will it work synchronously when it gets there. I had a situation where the value from the cache was not retrieved if you like (cannot prove it) and thought "may be is not thread safe" . What happens if 2 threads microseconds apart access the same AddCache-GetCache. what happens? – developer9969 Nov 06 '20 at 14:02
  • @Hostel very interesting , kind of in contrast what Evk said but may be I have not digested the full thread properly. The only reason I wrap it is for unit testing my methods – developer9969 Nov 06 '20 at 14:06

0 Answers0