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();
937100cookie-checkC# CacheManager / CachedValue