public class OverviewController : BaseApiController { private static readonly ThrottleDictionary throttleDictionary = new(); private readonly TimeSpan throttleTime = TimeSpan.FromSeconds(30); [HttpGet("api/day/{date}")] [ApiCallRight("api.get.day")] public async Task<VmCalendarList> GetDayOverview(DateTime date, bool isChanged = false) { var cacheKey = $"dayoverview-{date:yyyy-MM-dd}"; var throttle = throttleDictionary.Get(cacheKey, throttleTime); var value = (VmCalendarList)throttle.Value; if (!isChanged && !throttle.CanFire() && value != null) return value; await throttle.Build(async () => { var absenceInfo = await DomainPorts.OverviewService.DayOverview(date); var result = VmCalendarList.FromDomain(absenceInfo); throttle.Value = result; }); return (VmCalendarList)throttle.Value; } } public class ThrottleDictionary { private readonly ConcurrentDictionary<string, Throttle> keyValuePairs = new(); public Throttle Get(string key, TimeSpan throttleTime) { if (cleanupThrottle.CanFire()) Cleanup(); return keyValuePairs.GetOrAdd(key, (key) => new Throttle(throttleTime)); } private static readonly TimeSpan cleanupTime = TimeSpan.FromHours(1); private readonly Throttle cleanupThrottle = new(cleanupTime); private void Cleanup() { var keysToRemove = keyValuePairs.Where(x => x.Value.LastUsed() < DateTime.Now.Subtract(cleanupTime)).Select(x => x.Key); foreach(var key in keysToRemove) { keyValuePairs.Remove(key, out _); } } } public class Throttle { public Throttle(TimeSpan throttleTime) { ThrottleTime = throttleTime; } private DateTime? Last = null; private readonly TimeSpan ThrottleTime; private readonly object Locker = new(); public DateTime? LastUsed() => Last ?? DateTime.Now; public object Value { get; set; } public Task BuildTask; public async Task Build(Func<Task> fn) { if (BuildTask != null) { await BuildTask; return; } try { BuildTask = fn(); await BuildTask; } finally { BuildTask = null; } } public void Update() { Last = DateTime.Now; } public bool CanFire() { lock(Locker) { bool canFire = Last == null || (DateTime.Now - Last).Value > ThrottleTime; if (canFire) Update(); return canFire; } } }
722200cookie-checkC# Threaded throttle (api call)