// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // // // Throttles duplicate requests. // // -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Web.Helpers { using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; /// /// Throttles duplicate requests. /// Based loosely on /// public sealed class AsyncDuplicateLock { /// /// The collection of semaphore slims. /// private static readonly ConcurrentDictionary SemaphoreSlims = new ConcurrentDictionary(); /// /// Locks against the given key. /// /// /// The key that identifies the current object. /// /// /// The disposable . /// public IDisposable Lock(object key) { DisposableScope releaser = new DisposableScope( key, s => { SemaphoreSlim locker; if (SemaphoreSlims.TryRemove(s, out locker)) { locker.Release(); locker.Dispose(); } }); SemaphoreSlim semaphore = SemaphoreSlims.GetOrAdd(key, new SemaphoreSlim(1, 1)); semaphore.Wait(); return releaser; } /// /// Asynchronously locks against the given key. /// /// /// The key that identifies the current object. /// /// /// The disposable . /// public Task LockAsync(object key) { DisposableScope releaser = new DisposableScope( key, s => { SemaphoreSlim locker; if (SemaphoreSlims.TryRemove(s, out locker)) { locker.Release(); locker.Dispose(); } }); Task releaserTask = Task.FromResult(releaser as IDisposable); SemaphoreSlim semaphore = SemaphoreSlims.GetOrAdd(key, new SemaphoreSlim(1, 1)); Task waitTask = semaphore.WaitAsync(); return waitTask.IsCompleted ? releaserTask : waitTask.ContinueWith( (_, r) => (IDisposable)r, releaser, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } /// /// The disposable scope. /// private sealed class DisposableScope : IDisposable { /// /// The key /// private readonly object key; /// /// The close scope action. /// private readonly Action closeScopeAction; /// /// Initializes a new instance of the class. /// /// /// The key. /// /// /// The close scope action. /// public DisposableScope(object key, Action closeScopeAction) { this.key = key; this.closeScopeAction = closeScopeAction; } /// /// The dispose. /// public void Dispose() { this.closeScopeAction(this.key); } } } }