diff --git a/src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj b/src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj
index 82d3986a7..601953a5e 100644
--- a/src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj
+++ b/src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj
@@ -101,6 +101,9 @@
StringExtensions.cs
+
+ AsyncDeDuperLock.cs
+
CommonParameterParserUtility.cs
diff --git a/src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs b/src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs
new file mode 100644
index 000000000..7d6fbf0cd
--- /dev/null
+++ b/src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs
@@ -0,0 +1,134 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// 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 AsyncDeDuperLock
+ {
+ ///
+ /// The semaphore slims.
+ ///
+ private static readonly ConcurrentDictionary SemaphoreSlims = new ConcurrentDictionary();
+
+ ///
+ /// The lock.
+ ///
+ ///
+ /// The hash.
+ ///
+ ///
+ /// The .
+ ///
+ public IDisposable Lock(string 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;
+ }
+
+#if NET45 && !__MonoCS__
+ ///
+ /// The lock async.
+ ///
+ ///
+ /// The key.
+ ///
+ ///
+ /// The .
+ ///
+ public Task LockAsync(string 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);
+ }
+#endif
+ ///
+ /// The disposable scope.
+ ///
+ internal sealed class DisposableScope : IDisposable
+ {
+ ///
+ /// The key
+ ///
+ private readonly string key;
+
+ ///
+ /// The close scope action.
+ ///
+ private readonly Action closeScopeAction;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The key.
+ ///
+ ///
+ /// The close scope action.
+ ///
+ public DisposableScope(string key, Action closeScopeAction)
+ {
+ this.key = key;
+ this.closeScopeAction = closeScopeAction;
+ }
+
+ ///
+ /// The dispose.
+ ///
+ public void Dispose()
+ {
+ this.closeScopeAction(this.key);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs
index 0f4db86f3..2fe0a275d 100644
--- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs
+++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs
@@ -12,7 +12,6 @@ namespace ImageProcessor.Web.HttpModules
{
#region Using
using System;
- using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -22,7 +21,6 @@ namespace ImageProcessor.Web.HttpModules
using System.Security.Permissions;
using System.Security.Principal;
using System.Text.RegularExpressions;
- using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Hosting;
@@ -56,9 +54,9 @@ namespace ImageProcessor.Web.HttpModules
private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
///
- /// The collection of SemaphoreSlims for identifying given locking individual queries.
+ /// The locker for preventing duplicate requests.
///
- private static readonly ConcurrentDictionary SemaphoreSlims = new ConcurrentDictionary();
+ private static readonly AsyncDeDuperLock Locker = new AsyncDeDuperLock();
///
/// The value to prefix any remote image requests with to ensure they get captured.
@@ -149,20 +147,6 @@ namespace ImageProcessor.Web.HttpModules
GC.SuppressFinalize(this);
}
- ///
- /// Gets the specific for the given id.
- ///
- ///
- /// The id representing the .
- ///
- ///
- /// The for the given id.
- ///
- private static SemaphoreSlim GetSemaphoreSlim(string id)
- {
- return SemaphoreSlims.GetOrAdd(id, new SemaphoreSlim(1, 1));
- }
-
///
/// Disposes the object and frees resources for the Garbage Collector.
///
@@ -377,13 +361,11 @@ namespace ImageProcessor.Web.HttpModules
{
if (isRemote)
{
- SemaphoreSlim semaphore = GetSemaphoreSlim(cachedPath);
#if NET45 && !__MonoCS__
- await semaphore.WaitAsync();
+ using (await Locker.LockAsync(cachedPath))
#else
- semaphore.Wait();
+ using (Locker.Lock(cachedPath))
#endif
- try
{
Uri uri = new Uri(requestPath + "?" + urlParameters);
RemoteFile remoteFile = new RemoteFile(uri, false);
@@ -422,20 +404,14 @@ namespace ImageProcessor.Web.HttpModules
}
}
}
- finally
- {
- semaphore.Release();
- }
}
else
{
- SemaphoreSlim semaphore = GetSemaphoreSlim(cachedPath);
#if NET45 && !__MonoCS__
- await semaphore.WaitAsync();
+ using (await Locker.LockAsync(cachedPath))
#else
- semaphore.Wait();
+ using (Locker.Lock(cachedPath))
#endif
- try
{
// Check to see if the file exists.
// ReSharper disable once AssignNullToNotNullAttribute
@@ -460,10 +436,6 @@ namespace ImageProcessor.Web.HttpModules
// Trim the cache.
await cache.TrimCachedFolderAsync(cachedPath);
}
- finally
- {
- semaphore.Release();
- }
}
}
}
diff --git a/src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj b/src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj
index 40bd16bc7..6d4694400 100644
--- a/src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj
+++ b/src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj
@@ -56,6 +56,7 @@
+