using System;
using System.Threading.Tasks;
namespace Domain.Caches
{
public interface ICachedValue<T> : IDisposable
{
Task<T> GetValueAsync();
void ClearValue();
bool IsExpired();
}
public interface ICachedValueProvider
{
ICachedValue<T> CreateCachedValue<T>(
string key,
TimeSpan expiration,
Func<Task<T>> valueFactory,
TimeSpan? staleRevalidateWindow = null);
}
}
////
using System;
using System.Threading.Tasks;
using CacheAdapter.Caches;
using Domain.Caches;
using Microsoft.Extensions.Caching.Memory;
namespace CacheAdapter
{
public class CachedValueProvider : ICachedValueProvider
{
private readonly IMemoryCache _memoryCache;
public CachedValueProvider(IMemoryCache memoryCache)
{
_memoryCache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache));
}
public ICachedValue<T> CreateCachedValue<T>(string key, TimeSpan expiration, Func<Task<T>> valueFactory, TimeSpan? staleRevalidateWindow = null)
{
return new CachedValue<T>(
cache: _memoryCache,
key: key,
expiration: expiration,
valueFactory: valueFactory,
staleRevalidateWindow: staleRevalidateWindow
);
}
}
}
////
using System;
using System.Threading;
using System.Threading.Tasks;
using Domain.Caches;
using Microsoft.Extensions.Caching.Memory;
namespace CacheAdapter.Caches
{
public class CachedValue<T> : ICachedValue<T>
{
private readonly IMemoryCache _cache;
private readonly string _key;
private readonly Func<Task<T>> _valueFactory;
private readonly TimeSpan _expiration;
private readonly TimeSpan _staleRevalidateWindow;
private readonly SemaphoreSlim _semaphore = new(1, 1);
private DateTime _lastUpdated = DateTime.MinValue;
private bool _isRefreshing;
public CachedValue(IMemoryCache cache, string key, TimeSpan expiration, Func<Task<T>> valueFactory, ILogger logger = null, TimeSpan? staleRevalidateWindow = null)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_key = key ?? throw new ArgumentNullException(nameof(key));
_expiration = expiration;
_staleRevalidateWindow = staleRevalidateWindow ?? TimeSpan.FromSeconds(30);
_valueFactory = valueFactory ?? throw new ArgumentNullException(nameof(valueFactory));
}
public async Task<T> GetValueAsync()
{
// Probeer cached item op te halen
if (_cache.TryGetValue(_key, out T value))
{
var age = DateTime.UtcNow - _lastUpdated;
if (!_isRefreshing && age > _expiration - _staleRevalidateWindow)
TriggerBackgroundRefresh();
return value;
}
// Geen waarde in cache — geforceerde refresh
await _semaphore.WaitAsync();
try
{
if (_cache.TryGetValue(_key, out value)) return value;
value = await _valueFactory();
SetCache(value);
return value;
}
finally
{
_semaphore.Release();
}
}
private void TriggerBackgroundRefresh()
{
if (_isRefreshing) return;
_isRefreshing = true;
_ = Task.Run(async () =>
{
try
{
await _semaphore.WaitAsync();
var newValue = await _valueFactory();
SetCache(newValue);
}
catch (Exception ex)
{
// ignore
}
finally
{
_isRefreshing = false;
_semaphore.Release();
}
});
}
private void SetCache(T value)
{
_cache.Set(_key, value, new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = _expiration
});
_lastUpdated = DateTime.UtcNow;
}
public bool IsExpired()
{
if (_lastUpdated == DateTime.MinValue) return true;
var elapsed = DateTime.UtcNow - _lastUpdated;
return elapsed > _expiration;
}
public void ClearValue()
{
_cache.Remove(_key);
_lastUpdated = DateTime.MinValue;
}
public void Dispose() => _semaphore.Dispose();
}
}
Older:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
public interface ICachedValue : IDisposable
{
bool IsExpired();
}
public class CachedValue<T> : ICachedValue
{
private readonly Func<Task<T>> _valueFactory;
private readonly TimeSpan _expiration;
private T _cachedValue;
private DateTime _lastUpdated = DateTime.MinValue;
private readonly SemaphoreSlim _semaphore = new(1, 1);
public CachedValue(TimeSpan expiration, Func<Task<T>> valueFactory)
{
_expiration = expiration;
_valueFactory = valueFactory;
}
public async Task<T> GetValueAsync()
{
if (!IsExpired())
return _cachedValue;
await _semaphore.WaitAsync();
try
{
if (IsExpired())
{
_cachedValue = await _valueFactory();
_lastUpdated = DateTime.UtcNow;
}
return _cachedValue!;
}
finally
{
_semaphore.Release();
}
}
public void Reset() => _lastUpdated = DateTime.MinValue;
public bool IsExpired() => DateTime.UtcNow - _lastUpdated > _expiration;
public void Dispose()
{
_semaphore.Dispose();
}
}
public class CacheManager
{
private readonly ConcurrentDictionary<string, ICachedValue> _cache = new();
private DateTime _lastCleanup = DateTime.MinValue;
public TimeSpan CleanupInterval { get; set; } = TimeSpan.FromMinutes(5);
public DateTime LastCleanup => _lastCleanup;
public async Task<T> GetOrAddAsync<T>(string key, Func<Task<T>> valueFactory, TimeSpan expiration)
{
MaybeCleanup();
var existing = _cache.GetOrAdd(key, _ =>
new CachedValue<T>(expiration, valueFactory));
if (existing is CachedValue<T> typedCached)
{
return await typedCached.GetValueAsync();
}
else
{
throw new InvalidOperationException($"Cached value for key '{key}' has an unexpected type. Expected: {typeof(T).Name}, Actual: {existing.GetType().Name}");
}
}
public void Cleanup()
{
foreach (var kvp in _cache)
{
if (kvp.Value.IsExpired())
{
if (_cache.TryRemove(kvp.Key, out var removed))
{
removed.Dispose();
}
}
}
_lastCleanup = DateTime.UtcNow;
}
private void MaybeCleanup()
{
var now = DateTime.UtcNow;
if (now - _lastCleanup >= CleanupInterval)
{
Cleanup();
}
}
public int Count => _cache.Count;
}
Example use:
var cache = new CacheManager();
string key = "user:42";
// Cache een string
string value = await cache.GetOrAddAsync(key, async () =>
{
await Task.Delay(100);
return "John Doe";
}, TimeSpan.FromMinutes(5));
// Cache een int onder een andere key
int age = await cache.GetOrAddAsync("user:42:age", async () =>
{
await Task.Delay(50);
return 30;
}, TimeSpan.FromMinutes(1));
Old:
using System;
using System.Threading;
using System.Threading.Tasks;
public class CachedValue<T>
{
private readonly Func<Task<T>> _valueFactory;
private readonly TimeSpan _expiration;
private T _cachedValue;
private DateTime _lastUpdated = DateTime.MinValue;
private readonly SemaphoreSlim _semaphore = new(1, 1);
public CachedValue(TimeSpan expiration, Func<Task<T>> valueFactory)
{
_expiration = expiration;
_valueFactory = valueFactory;
}
public async Task<T> GetValueAsync()
{
var cachedValue = _cachedValue;
if (cachedValue != null && DateTime.UtcNow - _lastUpdated <= _expiration)
return cachedValue;
await _semaphore.WaitAsync();
try
{
if (_cachedValue == null || DateTime.UtcNow - _lastUpdated > _expiration)
{
_cachedValue = await _valueFactory();
_lastUpdated = DateTime.UtcNow;
}
return _cachedValue!;
}
finally
{
_semaphore.Release();
}
}
public void ResetAsync() => _lastUpdated = DateTime.MinValue;
}private readonly CachedValue<int> _summedValue;
public MyClass()
{
_summedValue = new CachedValue<int>(TimeSpan.FromMinutes(10), async () => await GetSummedValue());
}
public Task<int> GetSummedValueAsync() => _summedValue.GetValueAsync();With MemoryCache
using System;
using System.Runtime.Caching;
using System.Threading;
using System.Threading.Tasks;
public interface ICachedValue : IDisposable
{
bool IsExpired();
void Reset();
}
public class CachedValue<T> : ICachedValue
{
private static readonly MemoryCache Cache = MemoryCache.Default;
private readonly string _key;
private readonly Func<Task<T>> _valueFactory;
private readonly TimeSpan _expiration;
private readonly SemaphoreSlim _semaphore = new(1, 1);
private DateTime _lastUpdated = DateTime.MinValue;
public CachedValue(string key, TimeSpan expiration, Func<Task<T>> valueFactory)
{
_key = key ?? throw new ArgumentNullException(nameof(key));
_expiration = expiration;
_valueFactory = valueFactory ?? throw new ArgumentNullException(nameof(valueFactory));
}
public async Task<T> GetValueAsync()
{
if (Cache.Get(_key) is T cached && !IsExpired())
return cached;
await _semaphore.WaitAsync();
try
{
if (Cache.Get(_key) is T stillCached && !IsExpired())
return stillCached;
// Haal nieuwe waarde op
var newValue = await _valueFactory();
// Plaats in MemoryCache
var policy = new CacheItemPolicy
{
AbsoluteExpiration = DateTimeOffset.UtcNow.Add(_expiration)
};
Cache.Set(_key, newValue, policy);
_lastUpdated = DateTime.UtcNow;
return newValue;
}
finally
{
_semaphore.Release();
}
}
public void Reset()
{
Cache.Remove(_key);
_lastUpdated = DateTime.MinValue;
}
public bool IsExpired()
{
if (_lastUpdated == DateTime.MinValue) return true;
return DateTime.UtcNow - _lastUpdated > _expiration;
}
public void Dispose() => _semaphore.Dispose();
}
937100cookie-checkC# CacheManager / CachedValue