title: Asynchronous locking based on a key
body: <p>I'm attempting to figure out an issue that has been raised with my ImageProcessor library <a href="https://github.com/JimBobSquarePants/ImageProcessor/issues/189" rel="noreferrer">here</a> where I am getting intermittent file access errors when adding items to the cache.</p>
  
  <blockquote>
    <p>System.IO.IOException: The process cannot access the file 'D:\home\site\wwwroot\app_data\cache\0\6\5\f\2\7\065f27fc2c8e843443d210a1e84d1ea28bbab6c4.webp' because it is being used by another process.</p>
  </blockquote>
  
  <p>I wrote a class designed to perform an asynchronous lock based upon a key generated by a hashed url but it seems I have missed something in the implementation.</p>
  
  <p><strong>My locking class</strong> </p>
  
  <pre><code>public sealed class AsyncDuplicateLock
  {
      /// &lt;summary&gt;
      /// The collection of semaphore slims.
      /// &lt;/summary&gt;
      private static readonly ConcurrentDictionary&lt;object, SemaphoreSlim&gt; SemaphoreSlims
                              = new ConcurrentDictionary&lt;object, SemaphoreSlim&gt;();
  
      /// &lt;summary&gt;
      /// Locks against the given key.
      /// &lt;/summary&gt;
      /// &lt;param name="key"&gt;
      /// The key that identifies the current object.
      /// &lt;/param&gt;
      /// &lt;returns&gt;
      /// The disposable &lt;see cref="Task"/&gt;.
      /// &lt;/returns&gt;
      public IDisposable Lock(object key)
      {
          DisposableScope releaser = new DisposableScope(
          key,
          s =&gt;
          {
              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;
      }
  
      /// &lt;summary&gt;
      /// Asynchronously locks against the given key.
      /// &lt;/summary&gt;
      /// &lt;param name="key"&gt;
      /// The key that identifies the current object.
      /// &lt;/param&gt;
      /// &lt;returns&gt;
      /// The disposable &lt;see cref="Task"/&gt;.
      /// &lt;/returns&gt;
      public Task&lt;IDisposable&gt; LockAsync(object key)
      {
          DisposableScope releaser = new DisposableScope(
          key,
          s =&gt;
          {
              SemaphoreSlim locker;
              if (SemaphoreSlims.TryRemove(s, out locker))
              {
                  locker.Release();
                  locker.Dispose();
              }
          });
  
          Task&lt;IDisposable&gt; 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) =&gt; (IDisposable)r,
                         releaser,
                         CancellationToken.None,
                         TaskContinuationOptions.ExecuteSynchronously,
                         TaskScheduler.Default);
      }
  
      /// &lt;summary&gt;
      /// The disposable scope.
      /// &lt;/summary&gt;
      private sealed class DisposableScope : IDisposable
      {
          /// &lt;summary&gt;
          /// The key
          /// &lt;/summary&gt;
          private readonly object key;
  
          /// &lt;summary&gt;
          /// The close scope action.
          /// &lt;/summary&gt;
          private readonly Action&lt;object&gt; closeScopeAction;
  
          /// &lt;summary&gt;
          /// Initializes a new instance of the &lt;see cref="DisposableScope"/&gt; class.
          /// &lt;/summary&gt;
          /// &lt;param name="key"&gt;
          /// The key.
          /// &lt;/param&gt;
          /// &lt;param name="closeScopeAction"&gt;
          /// The close scope action.
          /// &lt;/param&gt;
          public DisposableScope(object key, Action&lt;object&gt; closeScopeAction)
          {
              this.key = key;
              this.closeScopeAction = closeScopeAction;
          }
  
          /// &lt;summary&gt;
          /// Disposes the scope.
          /// &lt;/summary&gt;
          public void Dispose()
          {
              this.closeScopeAction(this.key);
          }
      }
  }
  </code></pre>
  
  <p><strong>Usage - within a HttpModule</strong></p>
  
  <pre><code>private readonly AsyncDuplicateLock locker = new AsyncDuplicateLock();
  
  using (await this.locker.LockAsync(cachedPath))
  {
      // Process and save a cached image.
  }
  </code></pre>
  
  <p>Can anyone spot where I have gone wrong? I'm worried that I am misunderstanding something fundamental.</p>
  
  <p>The full source for the library is stored on Github <a href="https://github.com/JimBobSquarePants/ImageProcessor/tree/b207c6c463d368a19fda8afa4d865793761b8394/src/ImageProcessor.Web" rel="noreferrer">here</a></p>