Browse Source

Adding AsyncDeDuper

Former-commit-id: 30973a9ce646ad5301c4c68fbd66ddb2d37b8f29
pull/17/head
James South 12 years ago
parent
commit
08547cc821
  1. 3
      src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj
  2. 134
      src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs
  3. 40
      src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs
  4. 1
      src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj

3
src/ImageProcessor.Web/NET4/ImageProcessor.Web_NET4.csproj

@ -101,6 +101,9 @@
<Compile Include="..\NET45\Extensions\StringExtensions.cs">
<Link>StringExtensions.cs</Link>
</Compile>
<Compile Include="..\NET45\Helpers\AsyncDeDuperLock.cs">
<Link>AsyncDeDuperLock.cs</Link>
</Compile>
<Compile Include="..\NET45\Helpers\CommonParameterParserUtility.cs">
<Link>CommonParameterParserUtility.cs</Link>
</Compile>

134
src/ImageProcessor.Web/NET45/Helpers/AsyncDeDuperLock.cs

@ -0,0 +1,134 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="AsyncDeDuperLock.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Throttles duplicate requests.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Web.Helpers
{
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Throttles duplicate requests.
/// Based loosely on <see href="http://stackoverflow.com/a/21011273/427899"/>
/// </summary>
public sealed class AsyncDeDuperLock
{
/// <summary>
/// The semaphore slims.
/// </summary>
private static readonly ConcurrentDictionary<string, SemaphoreSlim> SemaphoreSlims = new ConcurrentDictionary<string, SemaphoreSlim>();
/// <summary>
/// The lock.
/// </summary>
/// <param name="key">
/// The hash.
/// </param>
/// <returns>
/// The <see cref="IDisposable"/>.
/// </returns>
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__
/// <summary>
/// The lock async.
/// </summary>
/// <param name="key">
/// The key.
/// </param>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
public Task<IDisposable> LockAsync(string key)
{
DisposableScope releaser = new DisposableScope(
key,
s =>
{
SemaphoreSlim locker;
if (SemaphoreSlims.TryRemove(s, out locker))
{
locker.Release();
locker.Dispose();
}
});
Task<IDisposable> 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
/// <summary>
/// The disposable scope.
/// </summary>
internal sealed class DisposableScope : IDisposable
{
/// <summary>
/// The key
/// </summary>
private readonly string key;
/// <summary>
/// The close scope action.
/// </summary>
private readonly Action<string> closeScopeAction;
/// <summary>
/// Initializes a new instance of the <see cref="DisposableScope"/> class.
/// </summary>
/// <param name="key">
/// The key.
/// </param>
/// <param name="closeScopeAction">
/// The close scope action.
/// </param>
public DisposableScope(string key, Action<string> closeScopeAction)
{
this.key = key;
this.closeScopeAction = closeScopeAction;
}
/// <summary>
/// The dispose.
/// </summary>
public void Dispose()
{
this.closeScopeAction(this.key);
}
}
}
}

40
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();
/// <summary>
/// The collection of SemaphoreSlims for identifying given locking individual queries.
/// The locker for preventing duplicate requests.
/// </summary>
private static readonly ConcurrentDictionary<string, SemaphoreSlim> SemaphoreSlims = new ConcurrentDictionary<string, SemaphoreSlim>();
private static readonly AsyncDeDuperLock Locker = new AsyncDeDuperLock();
/// <summary>
/// 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);
}
/// <summary>
/// Gets the specific <see cref="T:System.Threading.SemaphoreSlim"/> for the given id.
/// </summary>
/// <param name="id">
/// The id representing the <see cref="T:System.Threading.SemaphoreSlim"/>.
/// </param>
/// <returns>
/// The <see cref="T:System.Threading.SemaphoreSlim"/> for the given id.
/// </returns>
private static SemaphoreSlim GetSemaphoreSlim(string id)
{
return SemaphoreSlims.GetOrAdd(id, new SemaphoreSlim(1, 1));
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
@ -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();
}
}
}
}

1
src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj

@ -56,6 +56,7 @@
<Compile Include="Extensions\DirectoryInfoExtensions.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Helpers\CommonParameterParserUtility.cs" />
<Compile Include="Helpers\AsyncDeDuperLock.cs" />
<Compile Include="Helpers\NativeMethods.cs" />
<Compile Include="Helpers\ResourceHelpers.cs" />
<Compile Include="Helpers\ImageHelpers.cs" />

Loading…
Cancel
Save