108 lines
2.8 KiB
C#
108 lines
2.8 KiB
C#
using System.Collections.Concurrent;
|
|
|
|
namespace Harmonia.Core.Caching;
|
|
|
|
public abstract class Cache<TKey, TValue> : ICache<TKey, TValue> where TKey : notnull
|
|
{
|
|
private readonly ConcurrentDictionary<object, SemaphoreSlim> _locks;
|
|
private readonly SemaphoreSlim _throttler;
|
|
|
|
protected abstract int MaxConcurrentRequests { get; }
|
|
protected abstract TValue? TryGetValue(object key);
|
|
protected abstract ValueTask<TValue?> FetchAsync(TKey key, CancellationToken cancellationToken);
|
|
protected abstract void AddToCache(object key, TValue value);
|
|
|
|
public Cache()
|
|
{
|
|
_locks = new();
|
|
_throttler = new SemaphoreSlim(MaxConcurrentRequests, MaxConcurrentRequests);
|
|
}
|
|
|
|
public async Task<TValue?> GetAsync(TKey key, CancellationToken cancellationToken)
|
|
{
|
|
object? actualKey = GetKey(key);
|
|
|
|
if (actualKey == null)
|
|
return default;
|
|
|
|
TValue? value = TryGetValue(actualKey);
|
|
|
|
if (value != null)
|
|
return value;
|
|
|
|
SemaphoreSlim lockObject = _locks.GetOrAdd(actualKey, (key) => new SemaphoreSlim(1, 1));
|
|
|
|
bool throttlerAcquired = false;
|
|
bool lockAcquired = false;
|
|
|
|
try
|
|
{
|
|
await _throttler.WaitAsync(cancellationToken);
|
|
throttlerAcquired = true;
|
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
return default;
|
|
|
|
await lockObject.WaitAsync(cancellationToken);
|
|
lockAcquired = true;
|
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
return default;
|
|
|
|
return TryGetValue(actualKey) ?? await FetchAsync2(key, cancellationToken);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return default;
|
|
}
|
|
finally
|
|
{
|
|
if (lockAcquired)
|
|
lockObject.Release();
|
|
|
|
_locks.TryRemove(lockObject, out _);
|
|
|
|
if (throttlerAcquired)
|
|
_throttler.Release();
|
|
}
|
|
}
|
|
|
|
private async Task<TValue?> FetchAsync2(TKey key, CancellationToken cancellationToken)
|
|
{
|
|
object? actualKey = GetKey(key);
|
|
|
|
if (actualKey == null)
|
|
return default;
|
|
|
|
TValue? value = await FetchAsync(key, cancellationToken);
|
|
|
|
if (value == null)
|
|
return default;
|
|
|
|
AddToCache(actualKey, value);
|
|
|
|
return value;
|
|
}
|
|
|
|
public async Task<TValue?> RefreshAsync(TKey key, CancellationToken cancellationToken)
|
|
{
|
|
object? actualKey = GetKey(key);
|
|
|
|
if (actualKey == null)
|
|
return default;
|
|
|
|
TValue? value = await FetchAsync(key, cancellationToken);
|
|
|
|
if (value == null)
|
|
return default;
|
|
|
|
AddToCache(actualKey, value);
|
|
|
|
return value;
|
|
}
|
|
|
|
protected virtual object? GetKey(TKey key)
|
|
{
|
|
return key;
|
|
}
|
|
} |