diff --git a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs index da41a95734..a1b968cd86 100644 --- a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs @@ -19,6 +19,9 @@ namespace ImageProcessor.Web.HttpModules using ImageProcessor.Web.Caching; using ImageProcessor.Web.Config; using ImageProcessor.Web.Helpers; + using System.Collections.Concurrent; + using System.Threading.Tasks; + using System.Threading; #endregion /// @@ -46,18 +49,46 @@ namespace ImageProcessor.Web.HttpModules /// The assembly version. /// private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + + /// + /// The thread safe fifo queue. + /// + private static ConcurrentQueue imageOperations; + + /// + /// A value indicating whether the application has started. + /// + private static bool hasAppStarted = false; #endregion #region IHttpModule Members /// /// Initializes a module and prepares it to handle requests. /// - /// An that provides access to the methods, properties, and events common to all application objects within an ASP.NET application + /// + /// An that provides + /// access to the methods, properties, and events common to all + /// application objects within an ASP.NET application + /// public void Init(HttpApplication context) { - DiskCache.CreateCacheDirectories(); - context.BeginRequest += this.ContextBeginRequest; + if (!hasAppStarted) + { + lock (SyncRoot) + { + if (!hasAppStarted) + { + imageOperations = new ConcurrentQueue(); + DiskCache.CreateCacheDirectories(); + hasAppStarted = true; + } + } + } + + context.AddOnBeginRequestAsync(OnBeginAsync, OnEndAsync); + //context.BeginRequest += this.ContextBeginRequest; context.PreSendRequestHeaders += this.ContextPreSendRequestHeaders; + } /// @@ -69,15 +100,119 @@ namespace ImageProcessor.Web.HttpModules } #endregion + /// + /// The that starts asynchronous processing + /// of the . + /// + /// The source of the event. + /// + /// An EventArgs that contains + /// the event data. + /// + /// + /// The delegate to call when the asynchronous method call is complete. + /// If cb is null, the delegate is not called. + /// + /// + /// Any additional data needed to process the request. + /// + /// + IAsyncResult OnBeginAsync(object sender, EventArgs e, AsyncCallback cb, object extraData) + { + HttpContext context = ((HttpApplication)sender).Context; + EnqueueDelegate enqueueDelegate = new EnqueueDelegate(Enqueue); + + return enqueueDelegate.BeginInvoke(context, cb, extraData); + + } + + /// + /// The method that handles asynchronous events such as application events. + /// + /// + /// The that is the result of the + /// operation. + /// + public void OnEndAsync(IAsyncResult result) + { + // An action to consume the ConcurrentQueue. + Action action = () => + { + Action op; + + while (imageOperations.TryDequeue(out op)) + { + op(); + } + }; + + // Start 4 concurrent consuming actions. + Parallel.Invoke(action, action, action, action); + } + + /// + /// The delegate void representing the Enqueue method. + /// + /// + /// the HttpContext object that provides + /// references to the intrinsic server objects + /// + private delegate void EnqueueDelegate(HttpContext context); + + /// + /// Adds the method to the queue. + /// + /// + /// the HttpContext object that provides + /// references to the intrinsic server objects + /// + private void Enqueue(HttpContext context) + { + imageOperations.Enqueue(() => ProcessImage(context)); + } + /// /// Occurs as the first event in the HTTP pipeline chain of execution when ASP.NET responds to a request. /// /// The source of the event. /// An EventArgs that contains the event data. private void ContextBeginRequest(object sender, EventArgs e) + { + HttpContext context = ((HttpApplication)sender).Context; + imageOperations.Enqueue(() => ProcessImage(context)); + } + + /// + /// Occurs just before ASP.NET send HttpHeaders to the client. + /// + /// The source of the event. + /// An EventArgs that contains the event data. + private void ContextPreSendRequestHeaders(object sender, EventArgs e) { HttpContext context = ((HttpApplication)sender).Context; + object responseTypeObject = context.Items[CachedResponseTypeKey]; + + if (responseTypeObject != null) + { + string responseType = (string)responseTypeObject; + + this.SetHeaders(context, responseType); + + context.Items[CachedResponseTypeKey] = null; + } + } + + #region Private + /// + /// Processes the image. + /// + /// + /// the HttpContext object that provides + /// references to the intrinsic server objects + /// + private void ProcessImage(HttpContext context) + { // Is this a remote file. bool isRemote = context.Request.Path.Equals(RemotePrefix, StringComparison.OrdinalIgnoreCase); string path = string.Empty; @@ -133,43 +268,43 @@ namespace ImageProcessor.Web.HttpModules { if (responseStream != null) { - lock (SyncRoot) - { - // Trim the cache. - DiskCache.TrimCachedFolders(); + //lock (SyncRoot) + //{ + // Trim the cache. + DiskCache.TrimCachedFolders(); - responseStream.CopyTo(memoryStream); + responseStream.CopyTo(memoryStream); - imageFactory.Load(memoryStream) - .AddQueryString(queryString) - .Format(ImageUtils.GetImageFormat(imageName)) - .AutoProcess().Save(cachedPath); + imageFactory.Load(memoryStream) + .AddQueryString(queryString) + .Format(ImageUtils.GetImageFormat(imageName)) + .AutoProcess().Save(cachedPath); - // Ensure that the LastWriteTime property of the source and cached file match. - DateTime dateTime = DiskCache.SetCachedLastWriteTime(path, cachedPath, true); + // Ensure that the LastWriteTime property of the source and cached file match. + DateTime dateTime = DiskCache.SetCachedLastWriteTime(path, cachedPath, true); - // Add to the cache. - DiskCache.AddImageToCache(cachedPath, dateTime); - } + // Add to the cache. + DiskCache.AddImageToCache(cachedPath, dateTime); + //} } } } } else { - lock (SyncRoot) - { - // Trim the cache. - DiskCache.TrimCachedFolders(); + //lock (SyncRoot) + //{ + // Trim the cache. + DiskCache.TrimCachedFolders(); - imageFactory.Load(fullPath).AutoProcess().Save(cachedPath); + imageFactory.Load(fullPath).AutoProcess().Save(cachedPath); - // Ensure that the LastWriteTime property of the source and cached file match. - DateTime dateTime = DiskCache.SetCachedLastWriteTime(path, cachedPath, false); + // Ensure that the LastWriteTime property of the source and cached file match. + DateTime dateTime = DiskCache.SetCachedLastWriteTime(path, cachedPath, false); - // Add to the cache. - DiskCache.AddImageToCache(cachedPath, dateTime); - } + // Add to the cache. + DiskCache.AddImageToCache(cachedPath, dateTime); + //} } } } @@ -182,28 +317,6 @@ namespace ImageProcessor.Web.HttpModules } } - /// - /// Occurs just before ASP.NET send HttpHeaders to the client. - /// - /// The source of the event. - /// An EventArgs that contains the event data. - private void ContextPreSendRequestHeaders(object sender, EventArgs e) - { - HttpContext context = ((HttpApplication)sender).Context; - - object responseTypeObject = context.Items[CachedResponseTypeKey]; - - if (responseTypeObject != null) - { - string responseType = (string)responseTypeObject; - - this.SetHeaders(context, responseType); - - context.Items[CachedResponseTypeKey] = null; - } - } - - #region Private /// /// returns a value indicating whether a file exists. /// diff --git a/src/ImageProcessor.Web/ImageFactoryExtensions.cs b/src/ImageProcessor.Web/ImageFactoryExtensions.cs index a66bfeb432..93e383fae1 100644 --- a/src/ImageProcessor.Web/ImageFactoryExtensions.cs +++ b/src/ImageProcessor.Web/ImageFactoryExtensions.cs @@ -19,6 +19,11 @@ namespace ImageProcessor.Web /// public static class ImageFactoryExtensions { + /// + /// The object to lock against. + /// + private static readonly object SyncRoot = new object(); + /// /// Auto processes image files based on any query string parameters added to the image path. /// @@ -33,17 +38,20 @@ namespace ImageProcessor.Web { if (factory.ShouldProcess) { - // Get a list of all graphics processors that have parsed and matched the querystring. - List list = - ImageProcessorConfig.Instance.GraphicsProcessors - .Where(x => x.MatchRegexIndex(factory.QueryString) != int.MaxValue) - .OrderBy(y => y.SortOrder) - .ToList(); - - // Loop through and process the image. - foreach (IGraphicsProcessor graphicsProcessor in list) + lock (SyncRoot) { - factory.Image = graphicsProcessor.ProcessImage(factory); + // Get a list of all graphics processors that have parsed and matched the querystring. + List list = + ImageProcessorConfig.Instance.GraphicsProcessors + .Where(x => x.MatchRegexIndex(factory.QueryString) != int.MaxValue) + .OrderBy(y => y.SortOrder) + .ToList(); + + // Loop through and process the image. + foreach (IGraphicsProcessor graphicsProcessor in list) + { + factory.Image = graphicsProcessor.ProcessImage(factory); + } } }