title: cleaning up unused locks in a NamedReaderWriterLocker
body: <p>For doing asynchronous file IO I have made a class that lets me lock based upon a string key to prevent doing multiple writes at the same time to the same file or prevent having both writes and reads be done at the same time. The problem I face however is that its possible to grow the _lockDict by sending a different key each time. The old unused locks are not cleaned up but I do not know how I could do this in a threadsafe manner. This could potentially lead to a very big memory consumption.</p>
  
  <p>The class that gives back a instance of a lock based upon a key:</p>
  
  <pre><code>    public class AsyncNamedReaderWriterLocker
  {
      private readonly object _mutex = new object();
      private readonly Dictionary&lt;string, AsyncReaderWriterLock&gt; _lockDict = new Dictionary&lt;string, AsyncReaderWriterLock&gt;();
  
      public Task&lt;IDisposable&gt; EnterReaderLockAsync(string name)
      {
          var locker = GetLock(name);
          return locker.EnterReaderLockAsync();
      }
  
      public Task&lt;IDisposable&gt; EnterWriterLockAsync(string name)
      {
          var locker = GetLock(name);
          return locker.EnterWriterLockAsync();
      }
  
      private AsyncReaderWriterLock GetLock(string name)
      {
          lock (_mutex)
          {
              if (!_lockDict.TryGetValue(name, out AsyncReaderWriterLock locker))
              {
                  locker = new AsyncReaderWriterLock();
                  _lockDict.Add(name, locker);
              }
              return locker;
          }
      }
  </code></pre>
  
  <p>And the lock itself (idea from: <a href="https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-7-asyncreaderwriterlock/" rel="nofollow noreferrer">https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-7-asyncreaderwriterlock/</a>):</p>
  
  <pre><code>   public class AsyncReaderWriterLock
  {
      private readonly Queue&lt;TaskCompletionSource&lt;IDisposable&gt;&gt; _writerQueue = new Queue&lt;TaskCompletionSource&lt;IDisposable&gt;&gt;();
      private readonly Queue&lt;TaskCompletionSource&lt;IDisposable&gt;&gt; _readerQueue = new Queue&lt;TaskCompletionSource&lt;IDisposable&gt;&gt;();
      private readonly WriterLocker _writerLocker;
      private readonly ReaderLocker _readerLocker;
      private readonly object _mutex = new object();
      private int _locksHeld;
  
      public AsyncReaderWriterLock()
      {
          _writerLocker = new WriterLocker(this);
          _readerLocker = new ReaderLocker(this);
      }
  
      public Task&lt;IDisposable&gt; EnterReaderLockAsync()
      {
          lock (_mutex)
          {
              if (_locksHeld &gt;= 0 &amp;&amp; _writerQueue.Count == 0)
              {
                  _locksHeld++;
                  return Task.FromResult&lt;IDisposable&gt;(_readerLocker);
              }
              var tcs = new TaskCompletionSource&lt;IDisposable&gt;();
              _readerQueue.Enqueue(tcs);
              return tcs.Task;
          }
      }
  
      public Task&lt;IDisposable&gt; EnterWriterLockAsync()
      {
          lock (_mutex)
          {
              if (_locksHeld == 0)
              {
                  _locksHeld = -1;
                  return Task.FromResult&lt;IDisposable&gt;(_writerLocker);
              }
              var tcs = new TaskCompletionSource&lt;IDisposable&gt;();
              _writerQueue.Enqueue(tcs);
              return tcs.Task;
          }
      }
  
      private void ReleaseLocks()
      {
          if (_locksHeld != 0)
              return;
  
          // Give priority to writers.
          if (_writerQueue.Count != 0)
          {
              _locksHeld = -1;
              var tcs = _writerQueue.Dequeue();
              tcs.TrySetResult(_writerLocker);
              return;
          }
  
          // Then to readers.
          while (_readerQueue.Count != 0)
          {
              var tcs = _readerQueue.Dequeue();
              tcs.TrySetResult(_readerLocker);
              ++_locksHeld;
          }
      }
  
      private void ReleaseReaderLock()
      {
          lock (_mutex)
          {
              _locksHeld--;
              ReleaseLocks();
          }
      }
  
      private void ReleaseWriterLock()
      {
          lock (_mutex)
          {
              _locksHeld = 0;
              ReleaseLocks();
          }
      }
  
      private class ReaderLocker : IDisposable
      {
          private readonly AsyncReaderWriterLock _asyncReaderWriterLock;
  
          internal ReaderLocker(AsyncReaderWriterLock asyncReaderWriterLock)
          {
              _asyncReaderWriterLock = asyncReaderWriterLock;
          }
  
          public void Dispose()
          {
              _asyncReaderWriterLock.ReleaseReaderLock();
          }
      }
  
      private class WriterLocker : IDisposable
      {
          private readonly AsyncReaderWriterLock _asyncReaderWriterLock;
  
          internal WriterLocker(AsyncReaderWriterLock asyncReaderWriterLock)
          {
              _asyncReaderWriterLock = asyncReaderWriterLock;
          }
  
          public void Dispose()
          {
              _asyncReaderWriterLock.ReleaseWriterLock();
          }
      }
  }
  </code></pre>
  
  <p>Example of using the AsyncNamedReaderWriterLocker class:</p>
  
  <pre><code>    public class AsyncFileIO
  {
      private static readonly AsyncNamedReaderWriterLocker AsyncNamedReaderWriterLocker = new AsyncNamedReaderWriterLocker();
  
      public async Task CreateFile(string filename, byte[] data)
      {
          using (await AsyncNamedReaderWriterLocker.EnterWriterLockAsync(filename))
          {
              var directory = Path.GetDirectoryName(filename);
              if (!Directory.Exists(directory))
              {
                  Directory.CreateDirectory(directory);
              }
              using (var stream = File.Create(filename))
              {
                  await stream.WriteAsync(data, 0, data.Length);
              }
          }
      }
  
      public async Task&lt;byte[]&gt; ReadFile(string filename)
      {
          using (await AsyncNamedReaderWriterLocker.EnterReaderLockAsync(filename))
          {
              if (!File.Exists(filename)) return null;
              using (var stream = File.OpenRead(filename))
              {
                  var data = new byte[stream.Length];
                  await stream.ReadAsync(data, 0, 0);
                  return data;
              }
          }
      }
  
      public async Task DeleteFile(string filename)
      {
          using (await AsyncNamedReaderWriterLocker.EnterWriterLockAsync(filename))
          {
              var directoryName = Path.GetDirectoryName(filename);
              if (!Directory.Exists(directoryName)) return;
              File.Delete(filename);
          }
      }
  }
  </code></pre>
  
  <p>Note iam mostly interested in this for learning purposes and fully realize that this is overkill for most applications.</p>