Site icon Ryadel

ASP.NET Core C# - How to lock an async method according to custom IDs

ASP.NET Core C# - How to lock an async method according to custom IDs

If you're working with ASP.NET or ASP.NET Core C#  and you need to prevent (or restrict) the access and/or execution of certain methods from multiple threads, the best approach you can arguably use is a semaphore-based implementation using one of the various techniques provided by the framework's System.Threading namespace, such as the lock statement or the Semaphore and SemaphoreSlim classes. More specifically:

  • The lock statement acquires the mutual-exclusion lock for a given object, executes a statement block, and then releases the lock: while a lock is held, the thread that holds the lock can again acquire and release the lock. Any other thread is blocked from acquiring the lock and waits until the lock is released.
  • The Semaphore class represents a named (systemwide) or local semaphore: it is a thin wrapper around the Win32 semaphore object. Win32 semaphores are counting semaphores, which can be used to control access to a pool of resources.
  • The SemaphoreSlim class represents a lightweight, fast semaphore that can be used for waiting within a single process when wait times are expected to be very short. SemaphoreSlim relies as much as possible on synchronization primitives provided by the common language runtime (CLR): however, it also provides lazily initialized, kernel-based wait handles as necessary to support waiting on multiple semaphores.

Here's a typical lock implementation example:

In the above code, the static Main method content is locked and therefore accessible by a single thread at a time: all other threads will wait for the lock to be released before being allowed to access.

Here's a typical SemaphoreSlim usage example:

As we can see, the SemaphoreSlim implementation is more versatile than the lock statement, as it can be easily configured to allow a controlled number of threads: in the above example we've initialized the class to only allow a single thread, but we could easily change it to 2, 3 or more: furthermore, since the SemaphoreSlim class supports the WaitAsync() method, such approach can be used to lock async statements as well.

For additional info about the async/await pattern, the SynchronizationContext and the Task Asynchronous Programming model (TAP) in ASP.NET Core, we strongly suggest to read this post.

What I found to be missing in both lock and SemaphoreSlim approaches was a method to conditionally lock the thread based upon an arbitrary unique ID, which could be very useful in a number of common web-related scenarios, such as whenever we want to lock multiple threads executed by the same user from entering a given statement block, without restricting that block to other users/threads.

For that very reason I've put togheter LockProvider, a simple class that can be used to selectively lock objects, resources or statement blocks according to given unique IDs.

Here's the source code, released under MIT license and fully available on GitHub:

The above source code is for reference purposes only and might not be updated to the latest version: the up-to-date LockProvider source code, with some important improvements, is available on GitHub.

As we can see by looking at the code the class works with multiple SemaphoreSlim objects, hence can be used to perform synchronous or asynchronous locks.

Here's a sample usage that shows how the LockProvider class can be used to asynchronously lock a statement block:

To perform a synchronous lock, just use the Wait() method instead of WaitAsync().

Conclusions

That's it, at least for the time being: if you like the LockProvider class or want to share your feedbacks, feel free to use the comment section below or the GitHub issues page.

Exit mobile version