Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>That solution sure looks brittle and complex. Having public callbacks inside locks is bad practice. Why won't you let <code>LockProvider</code> return some sort of 'lock' objects, so that the consumers do the lock themselves. This separates the locking of the <code>locks</code> dictionary from the execution. It might look like this:</p> <pre><code>public class LockProvider { private readonly object globalLock = new object(); private readonly Dictionary&lt;String, Locker&gt; locks = new Dictionary&lt;string, Locker&gt;(StringComparer.Ordinal); public IDisposable Enter(string key) { Locker locker; lock (this.globalLock) { if (!this.locks.TryGetValue(key, out locker)) { this.locks[key] = locker = new Locker(this, key); } // Increase wait count ínside the global lock locker.WaitCount++; } // Call Enter and decrease wait count óutside the // global lock (to prevent deadlocks). locker.Enter(); // Only one thread will be here at a time for a given locker. locker.WaitCount--; return locker; } private sealed class Locker : IDisposable { private readonly LockProvider provider; private readonly string key; private object keyLock = new object(); public int WaitCount; public Locker(LockProvider provider, string key) { this.provider = provider; this.key = key; } public void Enter() { Monitor.Enter(this.keyLock); } public void Dispose() { if (this.keyLock != null) { this.Exit(); this.keyLock = null; } } private void Exit() { lock (this.provider.globalLock) { try { // Remove the key before releasing the lock, but // only when no threads are waiting (because they // will have a reference to this locker). if (this.WaitCount == 0) { this.provider.locks.Remove(this.key); } } finally { // Release the keyLock inside the globalLock. Monitor.Exit(this.keyLock); } } } } } </code></pre> <p>And the <code>LockProvider</code> can be used as follows:</p> <pre><code>public class Consumer { private LockProvider provider; public void DoStufOnFile(string fileName) { using (this.provider.Enter(fileName)) { // Long running operation on file here. } } } </code></pre> <p>Note that <code>Monitor.Enter</code> is called <em>before</em> we enter the <code>try</code> statement (using), which means in certain host environments (such as ASP.NET and SQL Server) we have the possibility of locks never being released when an asynchronous exception happens. Hosts like ASP.NET and SQL Server aggressively kill threads when timeouts occur. Rewriting this with the Enter outside the <code>Monitor.Enter</code> inside the <code>try</code> is a bit tricky though.</p> <p>I hope this helps.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload