Skip to content
Mark Cilia Vincenti edited this page Jan 16, 2024 · 10 revisions

GitHub Workflow Status NuGet NuGet Codacy Grade Codecov

An asynchronous .NET Standard 2.0 library that allows you to lock based on a key (keyed semaphores), limiting concurrent threads sharing the same key to a specified number.

For example, suppose you were processing financial transactions, but while working on one account you wouldn't want to concurrently process a transaction for the same account. Of course, you could just add a normal lock, but then you can only process one transaction at a time. If you're processing a transaction for account A, you may want to also be processing a separate transaction for account B. That's where AsyncKeyedLock comes in: it allows you to lock but only if the key matches.

The library uses two very different methods for locking, one using an underlying ConcurrentDictionary that's cleaned up after use whilst the other using a technique called striped locking.

Method 1: AsyncKeyedLocker, using an underlying ConcurrentDictionary

This is the traditional method. A dictionary will store all the keys currently in use and remove those no longer in use. In order to reduce memory allocations, it is advised to use pooling. This method is generally slower than striped locking, and could lead to more memory allocations, but unlike striped locking it guarantees that two threads using different keys can definitely be concurrently executed. Furthermore, this method cleans up after use and will therefore not retain a large number of semaphores permanently in memory even when it's not in use. This method works well for all scenarios, but it is especially recommended when its use is neither consistent throughout the day nor predictable. Learn how to use AsyncKeyedLocker.

Method 2: StripedAsyncKeyedLocker, using striped locking

This method basically creates an array of semaphores and from the key's hashcode will map it to a particular semaphore in the array. This method therefore does not need to do the bookkeeping necessary for the previous method and can be generally faster and with reduced memory allocations, however there are disadvantages to this method in that two different keys could get mapped to the same semaphore and will have to wait for each other just as if they shared the same key. Also, this method does not clean up after itself; all the semaphores will remain permanently in memory. Increasing the size of the array reduces the chances for collisions between different keys but will also consume more memory; thus there needs to be periodical maintenance to ensure that the array is sized accordingly, neither too large nor too small. This method works very well for some scenarios, especially when there is consistency in its use.

Warning: Since striped locking does not guarantee 1:1 mapping from a key to a semaphore, if two keyed locks depend on each other (e.g. nested locking), then they will deadlock if they share the same hashcode.

Learn how to use StripedAsyncKeyedLocker.

Non-keyed locking

A simple non-keyed lock is also available through AsyncNonKeyedLocker. Learn how to use AsyncNonKeyedLocker.