diff --git a/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs b/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs
index 24d46dc28..dbc1493a8 100644
--- a/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs
+++ b/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs
@@ -301,6 +301,7 @@ namespace ImageProcessor.Web.Caching
{
DateTime dateTime = imageFileInfo.LastWriteTimeUtc;
creationTime = cachedFileInfo.CreationTimeUtc;
+
cachedFileInfo.LastWriteTimeUtc = dateTime;
lastWriteTime = dateTime;
diff --git a/src/ImageProcessor.Web/NET45/Helpers/TaskHelpers.cs b/src/ImageProcessor.Web/NET45/Helpers/TaskHelpers.cs
index f06a14195..b4f339794 100644
--- a/src/ImageProcessor.Web/NET45/Helpers/TaskHelpers.cs
+++ b/src/ImageProcessor.Web/NET45/Helpers/TaskHelpers.cs
@@ -12,7 +12,7 @@ namespace ImageProcessor.Web.Helpers
{
#region Using
using System;
- using System.Threading.Tasks;
+ using System.Threading.Tasks;
#endregion
///
diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs
index a6a86f01d..6cf7df761 100644
--- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs
+++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs
@@ -12,6 +12,7 @@ namespace ImageProcessor.Web.HttpModules
{
#region Using
using System;
+ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -20,6 +21,7 @@ namespace ImageProcessor.Web.HttpModules
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;
+ using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Hosting;
@@ -51,6 +53,44 @@ namespace ImageProcessor.Web.HttpModules
/// The assembly version.
///
private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
+
+ ///
+ /// The collection of Semaphores for identifying given locking individual queries.
+ ///
+ private static readonly Dictionary Semaphores = new Dictionary();
+
+ ///
+ /// A value indicating whether this instance of the given entity has been disposed.
+ ///
+ /// if this instance has been disposed; otherwise, .
+ ///
+ /// If the entity is disposed, it must not be disposed a second
+ /// time. The isDisposed field is set the first time the entity
+ /// is disposed. If the isDisposed field is true, then the Dispose()
+ /// method will not dispose again. This help not to prolong the entity's
+ /// life in the Garbage Collector.
+ ///
+ private bool isDisposed;
+ #endregion
+
+ #region Destructors
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ///
+ /// Use C# destructor syntax for finalization code.
+ /// This destructor will run only if the Dispose method
+ /// does not get called.
+ /// It gives your base class the opportunity to finalize.
+ /// Do not provide destructors in types derived from this class.
+ ///
+ ~ImageProcessingModule()
+ {
+ // Do not re-create Dispose clean-up code here.
+ // Calling Dispose(false) is optimal in terms of
+ // readability and maintainability.
+ this.Dispose(false);
+ }
#endregion
#region IHttpModule Members
@@ -83,7 +123,65 @@ namespace ImageProcessor.Web.HttpModules
///
public void Dispose()
{
- // Nothing to dispose.
+ this.Dispose(true);
+
+ // This object will be cleaned up by the Dispose method.
+ // Therefore, you should call GC.SupressFinalize to
+ // take this object off the finalization queue
+ // and prevent finalization code for this object
+ // from executing a second time.
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Gets the specific for the given id.
+ ///
+ ///
+ /// The id representing the .
+ ///
+ ///
+ /// The for the given id.
+ ///
+ private static Semaphore GetSemaphore(string id)
+ {
+ id = id.ToMD5Fingerprint();
+
+ if (Semaphores.ContainsKey(id))
+ {
+ return Semaphores[id];
+ }
+
+ Semaphore semaphore = new Semaphore(1, 1, id);
+ Semaphores.Add(id, semaphore);
+ return semaphore;
+ }
+
+ ///
+ /// Disposes the object and frees resources for the Garbage Collector.
+ ///
+ /// If true, the object gets disposed.
+ private void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ // Dispose of any managed resources here.
+ foreach (KeyValuePair semaphore in Semaphores)
+ {
+ semaphore.Value.Dispose();
+ }
+
+ Semaphores.Clear();
+ }
+
+ // Call the appropriate methods to clean up
+ // unmanaged resources here.
+ // Note disposing is done.
+ this.isDisposed = true;
}
#endregion
@@ -294,19 +392,31 @@ namespace ImageProcessor.Web.HttpModules
{
responseStream.CopyTo(memoryStream);
- imageFactory.Load(memoryStream)
- .AddQueryString(queryString)
- .Format(ImageUtils.GetImageFormat(imageName))
- .AutoProcess().Save(cachedPath);
-
- // Ensure that the LastWriteTime property of the source and cached file match.
- Tuple creationAndLastWriteDateTimes = await cache.SetCachedLastWriteTimeAsync();
-
- // Add to the cache.
- cache.AddImageToCache(creationAndLastWriteDateTimes);
-
- // Trim the cache.
- await cache.TrimCachedFolderAsync(cachedPath);
+ Semaphore semaphore = GetSemaphore(cachedPath);
+ try
+ {
+ semaphore.WaitOne();
+
+ // Process the Image
+ imageFactory.Load(memoryStream)
+ .AddQueryString(queryString)
+ .Format(ImageUtils.GetImageFormat(imageName))
+ .AutoProcess()
+ .Save(cachedPath);
+
+ // Ensure that the LastWriteTime property of the source and cached file match.
+ Tuple creationAndLastWriteDateTimes = await cache.SetCachedLastWriteTimeAsync();
+
+ // Add to the cache.
+ cache.AddImageToCache(creationAndLastWriteDateTimes);
+
+ // Trim the cache.
+ await cache.TrimCachedFolderAsync(cachedPath);
+ }
+ finally
+ {
+ semaphore.Release();
+ }
}
}
}
@@ -323,16 +433,27 @@ namespace ImageProcessor.Web.HttpModules
throw new HttpException(404, "No image exists at " + fullPath);
}
- imageFactory.Load(fullPath).AutoProcess().Save(cachedPath);
+ Semaphore semaphore = GetSemaphore(cachedPath);
+ try
+ {
+ semaphore.WaitOne();
+
+ // Process the Image
+ imageFactory.Load(fullPath).AutoProcess().Save(cachedPath);
- // Ensure that the LastWriteTime property of the source and cached file match.
- Tuple creationAndLastWriteDateTimes = await cache.SetCachedLastWriteTimeAsync();
+ // Ensure that the LastWriteTime property of the source and cached file match.
+ Tuple creationAndLastWriteDateTimes = await cache.SetCachedLastWriteTimeAsync();
- // Add to the cache.
- cache.AddImageToCache(creationAndLastWriteDateTimes);
+ // Add to the cache.
+ cache.AddImageToCache(creationAndLastWriteDateTimes);
- // Trim the cache.
- await cache.TrimCachedFolderAsync(cachedPath);
+ // Trim the cache.
+ await cache.TrimCachedFolderAsync(cachedPath);
+ }
+ finally
+ {
+ semaphore.Release();
+ }
}
}
}
diff --git a/src/ImageProcessor.Web/NET45/Settings.StyleCop b/src/ImageProcessor.Web/NET45/Settings.StyleCop
index 253825567..578d6f188 100644
--- a/src/ImageProcessor.Web/NET45/Settings.StyleCop
+++ b/src/ImageProcessor.Web/NET45/Settings.StyleCop
@@ -1,4 +1,9 @@
+
+
+ Mutexes
+
+
diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs
index 91d011d7d..3a0ccf6cd 100644
--- a/src/ImageProcessor/ImageFactory.cs
+++ b/src/ImageProcessor/ImageFactory.cs
@@ -77,7 +77,6 @@ namespace ImageProcessor
// readability and maintainability.
this.Dispose(false);
}
-
#endregion
#region Properties