Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Named lock list #149

Open
bboyle1234 opened this issue Oct 12, 2018 · 5 comments
Open

Named lock list #149

bboyle1234 opened this issue Oct 12, 2018 · 5 comments
Assignees

Comments

@bboyle1234
Copy link

Would you consider adding this to the library?
Again, adapted from stephen cleary code found on stackoverflow and used regularly in my own libraries :)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Apex.TaskUtilities {

    /// <summary>
    /// Provides access to named asynchronous and synchronous locks. 
    /// IMPORTANT!! These locks are NOT re-entrant.
    /// IMPORTANT!! Does not work cross-app-domain because SemaphoreSLIM is used internally.
    /// </summary>
    public sealed class AsyncLockList {

        readonly Dictionary<object, SemaphoreReferenceCount> Semaphores = new Dictionary<object, SemaphoreReferenceCount>();

        SemaphoreSlim GetOrCreateSemaphore(object key) {
            lock (Semaphores) {
                if (Semaphores.TryGetValue(key, out var item)) {
                    item.IncrementCount();
                } else {
                    item = new SemaphoreReferenceCount();
                    Semaphores[key] = item;
                }
                return item.Semaphore;
            }
        }

        /// <summary>
        /// Synchronously blocks execution until the lock for the given key becomes available, and then blocks other
        /// requests until the IDisposable has been disposed. Useage:
        /// <code>
        /// using (lockList.Lock("nameOfTheLock")) { 
        ///   // do stuff inside lock
        /// }
        /// </code>
        /// IMPORTANT!! These locks are NOT re-entrant.
        /// IMPORTANT!! Does not work cross-app-domain because SemaphoreSLIM is used internally.
        /// </summary>
        /// <param name="key">A key to identify the requested lock.</param>
        /// <returns>An object which must be disposed to release the lock.</returns>
        public IDisposable Lock(object key) {
            GetOrCreateSemaphore(key).Wait();
            return new Releaser(Semaphores, key);
        }

        /// <summary>
        /// Asynchronously blocks execution until the lock for the given key becomes available, and then blocks other
        /// requests until the IDisposable has been disposed. Useage:
        /// <code>
        /// using (await lockList.LockAsync("nameOfTheLock")) { 
        ///   // do stuff inside lock
        /// }
        /// </code>
        /// IMPORTANT!! These locks are NOT re-entrant.
        /// IMPORTANT!! Does not work cross-app-domain because SemaphoreSLIM is used internally.
        /// </summary>
        /// <param name="key">A key to identify the requested lock.</param>
        /// <returns>An object which must be disposed to release the lock.</returns>
        public async Task<IDisposable> LockAsync(object key) {
            await GetOrCreateSemaphore(key).WaitAsync().ConfigureAwait(false);
            return new Releaser(Semaphores, key);
        }

        sealed class SemaphoreReferenceCount {
            public readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
            public int Count { get; private set; } = 1;

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public void IncrementCount() => Count++;

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public void DecrementCount() => Count--;
        }

        sealed class Releaser : IDisposable {
            readonly Dictionary<object, SemaphoreReferenceCount> Semaphores;
            readonly object Key;

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public Releaser(Dictionary<object, SemaphoreReferenceCount> semaphores, object key) {
                Semaphores = semaphores;
                Key = key;
            }

            public void Dispose() {
                lock (Semaphores) {
                    var item = Semaphores[Key];
                    item.DecrementCount();
                    if (item.Count == 0)
                        Semaphores.Remove(Key);
                    item.Semaphore.Release();
                }
            }
        }
    }
}
@StephenCleary StephenCleary self-assigned this Oct 12, 2018
@StephenCleary
Copy link
Owner

I'll consider it, but any changes like this need to be made across the whole library - i.e., we'd also need a named AsyncSemaphore, named AsyncManualResetEvent, etc.

@StephenCleary
Copy link
Owner

See also #16

I'm wondering if it would be possible to create some kind of naming wrapper?

@bboyle1234
Copy link
Author

I like this comment of yours from #16

Usually, when developers want a semaphore (or AsyncSemaphore) for each object, they just include it in the object itself

If I look over the code I wrote that uses the above object, I would probably find that I should have included an AsyncLock object in the context of the object being dealt with instead of creating a centralized repository of keyed locks.

@StephenCleary
Copy link
Owner

Another reference-counted approach: https://stackoverflow.com/a/31194647/263693

@CameronWills
Copy link

CameronWills commented Oct 23, 2019

  1. Both of these approaches lock the whole ConcurrentDictionary to perform an atomic decrement-count-and-remove operation. I was wondering if this is really necessary? or could you just lock the RefCounted item instead, and use Interlocked.Decrement() and Interlocked.Increment() to safely change the reference count and remove the item? What am I missing?

  2. Also, why do you would use a SemaphoreSlim instead of a Monitor? Is this just for Async support?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants