From b35c7236f9e100df7b203e8e9c40bdb7903aaf4c Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Sun, 23 Oct 2022 16:24:50 +0200 Subject: [PATCH] Optimisations, generics constructor and new overloaded methods for Lock / LockAsync --- .../AsyncKeyedLock.Tests.csproj | 6 +- AsyncKeyedLock/AsyncKeyedLock.csproj | 6 +- ...r.cs => AsyncKeyedLockReferenceCounter.cs} | 7 +- ...{Releaser.cs => AsyncKeyedLockReleaser.cs} | 6 +- AsyncKeyedLock/AsyncKeyedLocker.cs | 258 +++++++++++++++++- AsyncKeyedLock/AsyncKeyedLockerDictionary.cs | 30 +- AsyncKeyedLock/AsyncKeyedLocker{TKey}.cs | 132 --------- AsyncKeyedLock/IAsyncKeyedLocker.cs | 96 ++++++- README.md | 43 ++- 9 files changed, 422 insertions(+), 162 deletions(-) rename AsyncKeyedLock/{ReferenceCounter.cs => AsyncKeyedLockReferenceCounter.cs} (54%) rename AsyncKeyedLock/{Releaser.cs => AsyncKeyedLockReleaser.cs} (58%) delete mode 100644 AsyncKeyedLock/AsyncKeyedLocker{TKey}.cs diff --git a/AsyncKeyedLock.Tests/AsyncKeyedLock.Tests.csproj b/AsyncKeyedLock.Tests/AsyncKeyedLock.Tests.csproj index fe78e03..88efddc 100644 --- a/AsyncKeyedLock.Tests/AsyncKeyedLock.Tests.csproj +++ b/AsyncKeyedLock.Tests/AsyncKeyedLock.Tests.csproj @@ -9,7 +9,7 @@ - + @@ -22,8 +22,4 @@ - - - - diff --git a/AsyncKeyedLock/AsyncKeyedLock.csproj b/AsyncKeyedLock/AsyncKeyedLock.csproj index f8c850c..78589f2 100644 --- a/AsyncKeyedLock/AsyncKeyedLock.csproj +++ b/AsyncKeyedLock/AsyncKeyedLock.csproj @@ -7,7 +7,7 @@ https://github.com/MarkCiliaVincenti/AsyncKeyedLock.git https://github.com/MarkCiliaVincenti/AsyncKeyedLock LICENSE - 3.1.1 + 3.2.0 logo.png ForceRelease now releases everything in the semaphore before removing the key from the dictionary. An asynchronous .NET Standard 2.0 library that allows you to lock based on a key (keyed semaphores), only allowing a defined number of concurrent threads that share the same key. @@ -15,8 +15,8 @@ async,lock,key,semaphore,dictionary git false - 3.1.1.0 - 3.1.1.0 + 3.2.0.0 + 3.2.0.0 README.md true true diff --git a/AsyncKeyedLock/ReferenceCounter.cs b/AsyncKeyedLock/AsyncKeyedLockReferenceCounter.cs similarity index 54% rename from AsyncKeyedLock/ReferenceCounter.cs rename to AsyncKeyedLock/AsyncKeyedLockReferenceCounter.cs index 3c93ce7..ce3f72a 100644 --- a/AsyncKeyedLock/ReferenceCounter.cs +++ b/AsyncKeyedLock/AsyncKeyedLockReferenceCounter.cs @@ -2,7 +2,7 @@ namespace AsyncKeyedLock { - internal sealed class ReferenceCounter + internal sealed class AsyncKeyedLockReferenceCounter { private readonly TKey _key; public TKey Key => _key; @@ -12,10 +12,13 @@ internal sealed class ReferenceCounter private readonly SemaphoreSlim _semaphoreSlim; public SemaphoreSlim SemaphoreSlim => _semaphoreSlim; - public ReferenceCounter(TKey key, SemaphoreSlim semaphoreSlim) + public AsyncKeyedLockReleaser Releaser; + + public AsyncKeyedLockReferenceCounter(TKey key, SemaphoreSlim semaphoreSlim, AsyncKeyedLockerDictionary dictionary) { _key = key; _semaphoreSlim = semaphoreSlim; + Releaser = new AsyncKeyedLockReleaser(dictionary, this); } } } diff --git a/AsyncKeyedLock/Releaser.cs b/AsyncKeyedLock/AsyncKeyedLockReleaser.cs similarity index 58% rename from AsyncKeyedLock/Releaser.cs rename to AsyncKeyedLock/AsyncKeyedLockReleaser.cs index d6385af..009139e 100644 --- a/AsyncKeyedLock/Releaser.cs +++ b/AsyncKeyedLock/AsyncKeyedLockReleaser.cs @@ -3,12 +3,12 @@ namespace AsyncKeyedLock { - internal sealed class Releaser : IDisposable + internal sealed class AsyncKeyedLockReleaser : IDisposable { private readonly AsyncKeyedLockerDictionary _asyncKeyedLockerDictionary; - private readonly ReferenceCounter _referenceCounter; + private readonly AsyncKeyedLockReferenceCounter _referenceCounter; - public Releaser(AsyncKeyedLockerDictionary asyncKeyedLockerDictionary, ReferenceCounter referenceCounter) + public AsyncKeyedLockReleaser(AsyncKeyedLockerDictionary asyncKeyedLockerDictionary, AsyncKeyedLockReferenceCounter referenceCounter) { _asyncKeyedLockerDictionary = asyncKeyedLockerDictionary; _referenceCounter = referenceCounter; diff --git a/AsyncKeyedLock/AsyncKeyedLocker.cs b/AsyncKeyedLock/AsyncKeyedLocker.cs index 4c125ab..cff5ed4 100644 --- a/AsyncKeyedLock/AsyncKeyedLocker.cs +++ b/AsyncKeyedLock/AsyncKeyedLocker.cs @@ -5,16 +5,47 @@ namespace AsyncKeyedLock { + /// + /// AsyncKeyedLock class, inspired by Stephen Cleary's solution. + /// + public class AsyncKeyedLocker : AsyncKeyedLocker, IAsyncKeyedLocker + { + /// + /// Constructor for AsyncKeyedLock. + /// + public AsyncKeyedLocker() + { + } + + /// + /// Constructor for AsyncKeyedLock. + /// + /// The maximum number of requests for the semaphore that can be granted concurrently. Defaults to 1. + public AsyncKeyedLocker(int maxCount) + { + MaxCount = maxCount; + } + } + /// /// AsyncKeyedLock class, adapted and improved from Stephen Cleary's solution. /// - public class AsyncKeyedLocker : AsyncKeyedLocker + public class AsyncKeyedLocker : IAsyncKeyedLocker { + private readonly AsyncKeyedLockerDictionary _semaphoreSlims; + internal AsyncKeyedLockerDictionary SemaphoreSlims => _semaphoreSlims; + + /// + /// The maximum number of requests for the semaphore that can be granted concurrently. Defaults to 1. + /// + public int MaxCount { get; set; } = 1; + /// /// Constructor for AsyncKeyedLock. /// public AsyncKeyedLocker() { + _semaphoreSlims = new AsyncKeyedLockerDictionary(1); } /// @@ -24,6 +55,231 @@ public AsyncKeyedLocker() public AsyncKeyedLocker(int maxCount) { MaxCount = maxCount; + _semaphoreSlims = new AsyncKeyedLockerDictionary(maxCount); + } + + /// + /// Synchronously lock based on a key. + /// + /// The key to lock on. + /// A disposable value. + public IDisposable Lock(TKey key) + { + var referenceCounter = SemaphoreSlims.GetOrAdd(key); + referenceCounter.SemaphoreSlim.WaitAsync(); + return referenceCounter.Releaser; + } + + /// + /// Synchronously lock based on a key, while observing a . + /// + /// The key to lock on. + /// The to observe. + /// A disposable value. + public IDisposable Lock(TKey key, CancellationToken cancellationToken) + { + var referenceCounter = SemaphoreSlims.GetOrAdd(key); + referenceCounter.SemaphoreSlim.WaitAsync(cancellationToken); + return referenceCounter.Releaser; + } + + /// + /// Synchronously lock based on a key, setting a limit for the number of milliseconds to wait. + /// + /// The key to lock on. + /// The number of milliseconds to wait, (-1) to wait indefinitely, or zero to test the state of the wait handle and return immediately. + /// A disposable value. + public IDisposable Lock(TKey key, int millisecondsTimeout) + { + var referenceCounter = SemaphoreSlims.GetOrAdd(key); + referenceCounter.SemaphoreSlim.Wait(millisecondsTimeout); + return referenceCounter.Releaser; + } + + /// + /// Synchronously lock based on a key, setting a limit for the to wait. + /// + /// The key to lock on. + /// A that represents the number of milliseconds to wait, a that represents -1 milliseconds to wait indefinitely, or a that represents 0 milliseconds to test the wait handle and return immediately. + /// A disposable value. + public IDisposable Lock(TKey key, TimeSpan timeout) + { + var referenceCounter = SemaphoreSlims.GetOrAdd(key); + referenceCounter.SemaphoreSlim.Wait(timeout); + return referenceCounter.Releaser; + } + + /// + /// Synchronously lock based on a key, setting a limit for the number of milliseconds to wait, while observing a . + /// + /// The key to lock on. + /// The number of milliseconds to wait, (-1) to wait indefinitely, or zero to test the state of the wait handle and return immediately. + /// The to observe. + /// A disposable value. + public IDisposable Lock(TKey key, int millisecondsTimeout, CancellationToken cancellationToken) + { + var referenceCounter = SemaphoreSlims.GetOrAdd(key); + referenceCounter.SemaphoreSlim.Wait(millisecondsTimeout, cancellationToken); + return referenceCounter.Releaser; + } + + /// + /// Synchronously lock based on a key, setting a limit for the to wait, while observing a . + /// + /// The key to lock on. + /// A that represents the number of milliseconds to wait, a that represents -1 milliseconds to wait indefinitely, or a that represents 0 milliseconds to test the wait handle and return immediately. + /// The to observe. + /// A disposable value. + public IDisposable Lock(TKey key, TimeSpan timeout, CancellationToken cancellationToken) + { + var referenceCounter = SemaphoreSlims.GetOrAdd(key); + referenceCounter.SemaphoreSlim.Wait(timeout, cancellationToken); + return referenceCounter.Releaser; + } + + /// + /// Asynchronously lock based on a key. + /// + /// The key to lock on. + /// A disposable value. + public async Task LockAsync(TKey key) + { + var referenceCounter = SemaphoreSlims.GetOrAdd(key); + await referenceCounter.SemaphoreSlim.WaitAsync().ConfigureAwait(false); + return referenceCounter.Releaser; + } + + /// + /// Asynchronously lock based on a key, while observing a . + /// + /// The key to lock on. + /// The to observe. + /// A disposable value. + public async Task LockAsync(TKey key, CancellationToken cancellationToken) + { + var referenceCounter = SemaphoreSlims.GetOrAdd(key); + await referenceCounter.SemaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false); + return referenceCounter.Releaser; + } + + /// + /// Asynchronously lock based on a key, setting a limit for the number of milliseconds to wait. + /// + /// The key to lock on. + /// The number of milliseconds to wait, (-1) to wait indefinitely, or zero to test the state of the wait handle and return immediately. + /// A disposable value. + public async Task LockAsync(TKey key, int millisecondsTimeout) + { + var referenceCounter = SemaphoreSlims.GetOrAdd(key); + await referenceCounter.SemaphoreSlim.WaitAsync(millisecondsTimeout).ConfigureAwait(false); + return referenceCounter.Releaser; + } + + /// + /// Asynchronously lock based on a key, setting a limit for the to wait. + /// + /// The key to lock on. + /// A that represents the number of milliseconds to wait, a that represents -1 milliseconds to wait indefinitely, or a that represents 0 milliseconds to test the wait handle and return immediately. + /// A disposable value. + public async Task LockAsync(TKey key, TimeSpan timeout) + { + var referenceCounter = SemaphoreSlims.GetOrAdd(key); + await referenceCounter.SemaphoreSlim.WaitAsync(timeout).ConfigureAwait(false); + return referenceCounter.Releaser; + } + + /// + /// Asynchronously lock based on a key, setting a limit for the number of milliseconds to wait, while observing a . + /// + /// The key to lock on. + /// The number of milliseconds to wait, (-1) to wait indefinitely, or zero to test the state of the wait handle and return immediately. + /// The to observe. + /// A disposable value. + public async Task LockAsync(TKey key, int millisecondsTimeout, CancellationToken cancellationToken) + { + var referenceCounter = SemaphoreSlims.GetOrAdd(key); + await referenceCounter.SemaphoreSlim.WaitAsync(millisecondsTimeout, cancellationToken).ConfigureAwait(false); + return referenceCounter.Releaser; + } + + /// + /// Asynchronously lock based on a key, setting a limit for the to wait, while observing a . + /// + /// The key to lock on. + /// A that represents the number of milliseconds to wait, a that represents -1 milliseconds to wait indefinitely, or a that represents 0 milliseconds to test the wait handle and return immediately. + /// The to observe. + /// A disposable value. + public async Task LockAsync(TKey key, TimeSpan timeout, CancellationToken cancellationToken) + { + var referenceCounter = SemaphoreSlims.GetOrAdd(key); + await referenceCounter.SemaphoreSlim.WaitAsync(timeout, cancellationToken).ConfigureAwait(false); + return referenceCounter.Releaser; + } + + /// + /// Checks whether or not there is a thread making use of a keyed lock. + /// + /// The key requests are locked on. + /// if the key is in use; otherwise, false. + public bool IsInUse(TKey key) + { + return SemaphoreSlims.ContainsKey(key); + } + + /// + /// Get the number of requests concurrently locked for a given key. + /// + /// The key requests are locked on. + /// The number of requests. + [Obsolete("This method should not longer be used as it is confusing with Semaphore terminology. Use or instead depending what you want to do.")] + public int GetCount(TKey key) + { + return GetRemainingCount(key); + } + + /// + /// Get the number of requests concurrently locked for a given key. + /// + /// The key requests are locked on. + /// The number of requests concurrently locked for a given key. + public int GetRemainingCount(TKey key) + { + lock (SemaphoreSlims) + { + if (SemaphoreSlims.TryGetValue(key, out var referenceCounter)) + { + return referenceCounter.ReferenceCount; + } + return 0; + } + } + + /// + /// Get the number of remaining threads that can enter the lock for a given key. + /// + /// The key requests are locked on. + /// The number of remaining threads that can enter the lock for a given key. + public int GetCurrentCount(TKey key) + { + return MaxCount - GetRemainingCount(key); + } + + /// + /// Forces requests to be released from the semaphore. + /// + /// The key requests are locked on. + /// if the key is successfully found and removed; otherwise, false. + public bool ForceRelease(TKey key) + { + lock (SemaphoreSlims) + { + if (SemaphoreSlims.TryGetValue(key, out var referenceCounter)) + { + referenceCounter.SemaphoreSlim.Release(referenceCounter.ReferenceCount); + return SemaphoreSlims.TryRemove(key, out _); + } + return false; + } } } } diff --git a/AsyncKeyedLock/AsyncKeyedLockerDictionary.cs b/AsyncKeyedLock/AsyncKeyedLockerDictionary.cs index 4793405..5762e3c 100644 --- a/AsyncKeyedLock/AsyncKeyedLockerDictionary.cs +++ b/AsyncKeyedLock/AsyncKeyedLockerDictionary.cs @@ -7,10 +7,29 @@ namespace AsyncKeyedLock { - internal sealed class AsyncKeyedLockerDictionary : ConcurrentDictionary> + internal sealed class AsyncKeyedLockerDictionary : ConcurrentDictionary> { - public ReferenceCounter GetOrAdd(TKey key, int maxCount) + private readonly int _maxCount; + public AsyncKeyedLockerDictionary(int maxCount) { + _maxCount = maxCount; + } + + public AsyncKeyedLockReferenceCounter GetOrAdd(TKey key) + { + if (TryGetValue(key, out var firstReferenceCounter) && Monitor.TryEnter(firstReferenceCounter)) + { + ++firstReferenceCounter.ReferenceCount; + Monitor.Exit(firstReferenceCounter); + return firstReferenceCounter; + } + + firstReferenceCounter = new AsyncKeyedLockReferenceCounter(key, new SemaphoreSlim(_maxCount), this); + if (TryAdd(key, firstReferenceCounter)) + { + return firstReferenceCounter; + } + while (true) { if (TryGetValue(key, out var referenceCounter) && Monitor.TryEnter(referenceCounter)) @@ -20,15 +39,14 @@ public ReferenceCounter GetOrAdd(TKey key, int maxCount) return referenceCounter; } - referenceCounter = new ReferenceCounter(key, new SemaphoreSlim(maxCount)); - if (TryAdd(key, referenceCounter)) + if (TryAdd(key, firstReferenceCounter)) { - return referenceCounter; + return firstReferenceCounter; } } } - public void Release(ReferenceCounter referenceCounter) + public void Release(AsyncKeyedLockReferenceCounter referenceCounter) { while (!Monitor.TryEnter(referenceCounter)) { } diff --git a/AsyncKeyedLock/AsyncKeyedLocker{TKey}.cs b/AsyncKeyedLock/AsyncKeyedLocker{TKey}.cs deleted file mode 100644 index 34ee0e6..0000000 --- a/AsyncKeyedLock/AsyncKeyedLocker{TKey}.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace AsyncKeyedLock -{ - /// - /// AsyncKeyedLock class, adapted and improved from Stephen Cleary's solution. - /// - public class AsyncKeyedLocker// : IAsyncKeyedLocker - { - private readonly AsyncKeyedLockerDictionary _semaphoreSlims = new AsyncKeyedLockerDictionary(); - internal AsyncKeyedLockerDictionary SemaphoreSlims => _semaphoreSlims; - - /// - /// The maximum number of requests for the semaphore that can be granted concurrently. Defaults to 1. - /// - public int MaxCount { get; set; } = 1; - - /// - /// Constructor for AsyncKeyedLock. - /// - public AsyncKeyedLocker() - { - } - - /// - /// Constructor for AsyncKeyedLock. - /// - /// The maximum number of requests for the semaphore that can be granted concurrently. Defaults to 1. - public AsyncKeyedLocker(int maxCount) - { - MaxCount = maxCount; - } - - //private ReferenceCounter GetOrAdd(TKey key) - //{ - // return SemaphoreSlims.GetOrAdd(key, MaxCount); - //} - - ///// - ///// Synchronously lock based on a key - ///// - ///// The key to lock on. - ///// A disposable value. - //public IDisposable Lock(TKey key) - //{ - // var referenceCounter = GetOrAdd(key); - // referenceCounter.SemaphoreSlim.Wait(); - // return new Releaser(SemaphoreSlims, referenceCounter); - //} - - /// - /// Asynchronously lock based on a key - /// - /// The key to lock on. - /// A disposable value. - public async Task LockAsync(TKey key) - { - var referenceCounter = SemaphoreSlims.GetOrAdd(key, MaxCount); - await referenceCounter.SemaphoreSlim.WaitAsync().ConfigureAwait(false); - return new Releaser(SemaphoreSlims, referenceCounter); - } - - ///// - ///// Checks whether or not there is a thread making use of a keyed lock. - ///// - ///// The key requests are locked on. - ///// if the key is in use; otherwise, false. - //public bool IsInUse(TKey key) - //{ - // return SemaphoreSlims.ContainsKey(key); - //} - - ///// - ///// Get the number of requests concurrently locked for a given key. - ///// - ///// The key requests are locked on. - ///// The number of requests. - //[Obsolete("This method should not longer be used as it is confusing with Semaphore terminology. Use or instead depending what you want to do.")] - //public int GetCount(TKey key) - //{ - // return GetRemainingCount(key); - //} - - ///// - ///// Get the number of requests concurrently locked for a given key. - ///// - ///// The key requests are locked on. - ///// The number of requests concurrently locked for a given key. - //public int GetRemainingCount(TKey key) - //{ - // lock (SemaphoreSlims) - // { - // if (SemaphoreSlims.TryGetValue(key, out var referenceCounter)) - // { - // return referenceCounter.ReferenceCount; - // } - // return 0; - // } - //} - - ///// - ///// Get the number of remaining threads that can enter the lock for a given key. - ///// - ///// The key requests are locked on. - ///// The number of remaining threads that can enter the lock for a given key. - //public int GetCurrentCount(TKey key) - //{ - // return MaxCount - GetRemainingCount(key); - //} - - ///// - ///// Forces requests to be released from the semaphore. - ///// - ///// The key requests are locked on. - ///// if the key is successfully found and removed; otherwise, false. - //public bool ForceRelease(TKey key) - //{ - // lock (SemaphoreSlims) - // { - // if (SemaphoreSlims.TryGetValue(key, out var referenceCounter)) - // { - // referenceCounter.SemaphoreSlim.Release(referenceCounter.ReferenceCount); - // return SemaphoreSlims.TryRemove(key, out _); - // } - // return false; - // } - //} - } -} diff --git a/AsyncKeyedLock/IAsyncKeyedLocker.cs b/AsyncKeyedLock/IAsyncKeyedLocker.cs index 9983930..f227ee1 100644 --- a/AsyncKeyedLock/IAsyncKeyedLocker.cs +++ b/AsyncKeyedLock/IAsyncKeyedLocker.cs @@ -1,8 +1,16 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace AsyncKeyedLock { + /// + /// AsyncKeyedLock interface + /// + public interface IAsyncKeyedLocker : IAsyncKeyedLocker + { + } + /// /// AsyncKeyedLock interface /// @@ -14,19 +22,103 @@ public interface IAsyncKeyedLocker int MaxCount { get; set; } /// - /// Synchronously lock based on a key + /// Synchronously lock based on a key. /// /// The key to lock on. /// A disposable value. IDisposable Lock(TKey key); /// - /// Asynchronously lock based on a key + /// Synchronously lock based on a key, while observing a . + /// + /// The key to lock on. + /// The to observe. + /// A disposable value. + IDisposable Lock(TKey key, CancellationToken cancellationToken); + + /// + /// Synchronously lock based on a key, setting a limit for the number of milliseconds to wait. + /// + /// The key to lock on. + /// The number of milliseconds to wait, (-1) to wait indefinitely, or zero to test the state of the wait handle and return immediately. + /// A disposable value. + IDisposable Lock(TKey key, int millisecondsTimeout); + + /// + /// Synchronously lock based on a key, setting a limit for the to wait. + /// + /// The key to lock on. + /// A that represents the number of milliseconds to wait, a that represents -1 milliseconds to wait indefinitely, or a that represents 0 milliseconds to test the wait handle and return immediately. + /// A disposable value. + IDisposable Lock(TKey key, TimeSpan timeout); + + /// + /// Synchronously lock based on a key, setting a limit for the number of milliseconds to wait, while observing a . + /// + /// The key to lock on. + /// The number of milliseconds to wait, (-1) to wait indefinitely, or zero to test the state of the wait handle and return immediately. + /// The to observe. + /// A disposable value. + IDisposable Lock(TKey key, int millisecondsTimeout, CancellationToken cancellationToken); + + /// + /// Synchronously lock based on a key, setting a limit for the to wait, while observing a . + /// + /// The key to lock on. + /// A that represents the number of milliseconds to wait, a that represents -1 milliseconds to wait indefinitely, or a that represents 0 milliseconds to test the wait handle and return immediately. + /// The to observe. + /// A disposable value. + IDisposable Lock(TKey key, TimeSpan timeout, CancellationToken cancellationToken); + + /// + /// Asynchronously lock based on a key. /// /// The key to lock on. /// A disposable value. Task LockAsync(TKey key); + /// + /// Asynchronously lock based on a key, while observing a . + /// + /// The key to lock on. + /// The to observe. + /// A disposable value. + Task LockAsync(TKey key, CancellationToken cancellationToken); + + /// + /// Asynchronously lock based on a key, setting a limit for the number of milliseconds to wait. + /// + /// The key to lock on. + /// The number of milliseconds to wait, (-1) to wait indefinitely, or zero to test the state of the wait handle and return immediately. + /// A disposable value. + Task LockAsync(TKey key, int millisecondsTimeout); + + /// + /// Asynchronously lock based on a key, setting a limit for the to wait. + /// + /// The key to lock on. + /// A that represents the number of milliseconds to wait, a that represents -1 milliseconds to wait indefinitely, or a that represents 0 milliseconds to test the wait handle and return immediately. + /// A disposable value. + Task LockAsync(TKey key, TimeSpan timeout); + + /// + /// Asynchronously lock based on a key, setting a limit for the number of milliseconds to wait, while observing a . + /// + /// The key to lock on. + /// The number of milliseconds to wait, (-1) to wait indefinitely, or zero to test the state of the wait handle and return immediately. + /// The to observe. + /// A disposable value. + Task LockAsync(TKey key, int millisecondsTimeout, CancellationToken cancellationToken); + + /// + /// Asynchronously lock based on a key, setting a limit for the to wait, while observing a . + /// + /// The key to lock on. + /// A that represents the number of milliseconds to wait, a that represents -1 milliseconds to wait indefinitely, or a that represents 0 milliseconds to test the wait handle and return immediately. + /// The to observe. + /// A disposable value. + Task LockAsync(TKey key, TimeSpan timeout, CancellationToken cancellationToken); + /// /// Checks whether or not there is a thread making use of a keyed lock. /// diff --git a/README.md b/README.md index 6bb0783..a11ae76 100644 --- a/README.md +++ b/README.md @@ -6,24 +6,53 @@ An asynchronous .NET Standard 2.0 library that allows you to lock based on a key For example, if you're processing transactions, you may want to limit to only one transaction per user so that the order is maintained, but meanwhile allowing parallel processing of multiple users. ## Benchmarks -Tests show that AsyncKeyedLock is [considerably faster than similar libraries](https://github.com/MarkCiliaVincenti/AsyncKeyedLockBenchmarks) due to its neat locking mechanism. +Tests show that AsyncKeyedLock is [faster than similar libraries, while consuming less memory](https://github.com/MarkCiliaVincenti/AsyncKeyedLockBenchmarks). ## Installation The recommended means is to use [NuGet](https://www.nuget.org/packages/AsyncKeyedLock), but you could also download the source code from [here](https://github.com/MarkCiliaVincenti/AsyncKeyedLock/releases). ## Usage +You need to start off with creating an instance of `AsyncKeyedLocker` or `AsyncKeyedLocker`. The recommended way is to use the latter, which consumes less memory. The former uses `object` and may be slightly faster, but at the expense of higher memory usage. + +### Dependency injection +```csharp +services.AddSingleton(); +``` + +or: + +```csharp +services.AddSingleton, AsyncKeyedLocker>(); +``` + +### Variable instantiation ```csharp var asyncKeyedLocker = new AsyncKeyedLocker(); +``` + +or: + +```csharp +var asyncKeyedLocker = new AsyncKeyedLocker(); +``` + +or if you would like to set the maximum number of requests for the semaphore that can be granted concurrently (set to 1 by default): + +```csharp +var asyncKeyedLocker = new AsyncKeyedLocker(2); +``` + +### Locking +```csharp using (var lockObj = await asyncKeyedLocker.LockAsync(myObject)) { ... } ``` -You can also set the maximum number of requests for the semaphore that can be granted concurrently (set to 1 by default): -```csharp -var asyncKeyedLocker = new AsyncKeyedLocker(2); -``` +There are other overloaded methods for `LockAsync` which allow you to use `CancellationToken`, milliseconds timeout, `System.TimeSpan` or a combination of these. + +There are also synchronous `Lock` methods available. If you would like to see how many concurrent requests there are for a semaphore for a given key: ```csharp @@ -45,7 +74,5 @@ And if for some reason you need to force release the requests in the semaphore f asyncKeyedLocker.ForceRelease(myObject); ``` -You may also use Dependency Injection to inject an instance of AsyncKeyedLock. - ## Credits -This library is based on [Stephen Cleary's solution](https://stackoverflow.com/questions/31138179/asynchronous-locking-based-on-a-key/31194647#31194647). +This library was inspired by [Stephen Cleary's solution](https://stackoverflow.com/questions/31138179/asynchronous-locking-based-on-a-key/31194647#31194647).