Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using Microsoft.Extensions.Caching.Memory;
- using Microsoft.Extensions.Primitives;
- using System;
- using System.Collections.Concurrent;
- using System.Threading;
- using System.Threading.Tasks;
- namespace MFL.Caching
- {
- public static class CacheConfigurator
- {
- public static void ConfigureCacheAccess(this IServiceCollection serviceList)
- {
- serviceList.AddMemoryCache();
- serviceList.TryAddSingleton<ICacheService, CacheService>();
- }
- }
- public interface ICacheService : IDisposable
- {
- void ResetCache();
- Task<TItem> GetOrCreateAsync<TItem>(string prefix, object key, Func<ICacheEntry, Task<TItem>> factory, CancellationToken token);
- TItem GetOrCreate<TItem>(string prefix, object key, Func<ICacheEntry, TItem> factory, CancellationToken token);
- }
- internal class CacheService : ICacheService
- {
- private static readonly ConcurrentDictionary<string, SemaphoreSlim> SemaphoreDictionary = new ConcurrentDictionary<string, SemaphoreSlim>();
- public CancellationTokenSource ResetCacheToken { get; set; } = new CancellationTokenSource();
- private readonly IMemoryCache _cache;
- public CacheService(IMemoryCache cache) => _cache = cache;
- public void Dispose() => ResetCacheToken.Dispose();
- public async Task<TItem> GetOrCreateAsync<TItem>(string prefix, object key, Func<ICacheEntry, Task<TItem>> factory, CancellationToken token)
- {
- var svcKey = $"{prefix}_{key}";
- //Here the semaphore avoids running 2 factories in case we are requesting the same key at the same time.
- //It's basically an implementation of the "lock" mechanism but for asynchronous code
- var semaphore = SemaphoreDictionary.GetOrAdd(svcKey, new SemaphoreSlim(1));
- await semaphore.WaitAsync(token).ConfigureAwait(false);
- try
- {
- if (_cache.TryGetValue(svcKey, out var value))
- return (TItem)value;
- var entry = CreateCancellableCacheEntry(svcKey);
- var createdVal = await factory(entry).ConfigureAwait(false);
- if (createdVal == null)
- throw new NullReferenceException();
- SetEntryValue(entry, createdVal);
- return createdVal;
- }
- finally
- {
- semaphore.Release();
- }
- }
- public TItem GetOrCreate<TItem>(string prefix, object key, Func<ICacheEntry, TItem> factory, CancellationToken token)
- {
- var svcKey = $"{prefix}_{key}";
- //Here the semaphore avoids running 2 factories in case we are requesting the same key at the same time.
- //It's basically an implementation of the "lock" mechanism but for asynchronous code
- var semaphore = SemaphoreDictionary.GetOrAdd(svcKey, new SemaphoreSlim(1));
- semaphore.Wait(token);
- try
- {
- if (_cache.TryGetValue(svcKey, out var value)) return (TItem)value;
- var entry = CreateCancellableCacheEntry(svcKey);
- var createdVal = factory(entry);
- if (createdVal == null)
- throw new NullReferenceException();
- SetEntryValue(entry, createdVal);
- return createdVal;
- }
- finally
- {
- semaphore.Release();
- }
- }
- public void ResetCache()
- {
- if (ResetCacheToken?.IsCancellationRequested == false && ResetCacheToken.Token.CanBeCanceled)
- {
- ResetCacheToken.Cancel();
- ResetCacheToken.Dispose();
- }
- ResetCacheToken = new CancellationTokenSource();
- }
- private static void SetEntryValue(ICacheEntry entry, object value)
- {
- entry.SetValue(value);
- entry.Dispose();
- }
- private ICacheEntry CreateCancellableCacheEntry(object key)
- {
- var entry = _cache.CreateEntry(key);
- entry.AddExpirationToken(new CancellationChangeToken(ResetCacheToken.Token));
- return entry;
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement