I see many methods across new framework that uses new asynchronous pattern/language support for async/await
in C#. Why is there no Monitor.EnterAsync()
or other async lock
mechanism that releases current thread & returns as soon as lock
is available?
I assume that this is not possible - question is why?
While there is no asynchronous monitor in .NET by default, Stephen Cleary has a great library AsyncEx which deals with synchronization issues when using async/await.
It has an AsyncMonitor
class, which does pretty much exactly what you're looking for. You can get it either from GitHub or as a NuGet package.
Usage example:
var monitor = new AsyncMonitor();
using (await monitor.EnterAsync())
{
// Critical section
}
This worked well for me as detailed here: SemaphoreSlim Class
Semaphores are of two types: local semaphores and named system semaphores.
The former is local to an app. The latter is visible throughout the operating system and is suitable for inter-process synchronization.
The SemaphoreSlim is a lightweight alternative to the Semaphore class that doesn't use Windows kernel semaphores. Unlike the Semaphore class, the SemaphoreSlim class doesn't support named system semaphores.
You can use it as a local semaphore only. The SemaphoreSlim class is the recommended semaphore for synchronization within a single app.
public class ResourceLocker
{
private Dictionary<string, SemaphoreSlim> _lockers = null;
private object lockObj = new object();
public ResourceLocker()
{
_lockers = new Dictionary<string, SemaphoreSlim>();
}
public SemaphoreSlim GetOrCreateLocker(string resource)
{
lock (lockObj)
{
if (!_lockers.ContainsKey(resource))
{
_lockers.Add(resource, new SemaphoreSlim(1, 1));
}
return _lockers?[resource];
}
}
public bool ReleaseLocker(string resource)
{
lock (lockObj)
{
if (_lockers.ContainsKey(resource))
{
var locker = _lockers?[resource];
if (locker != null)
{
locker.Release();
return true;
}
_lockers.Remove(resource);
}
return false;
}//lock
}
}
Usage
var resource = "customResource";
var someObject = new SomeObject();
SomeResponse response = null;
var resourceLocker = new ResourceLocker();
try
{
var semaSlim = resourceLocker.GetOrCreateLocker(resource);
semaSlim.Wait();
response = someObject.DoSomething();
}
finally
{
resourceLocker.ReleaseLocker(resource);
}
Async
Task.Run(async ()=>{
var semaSlim = resourceLocker.GetOrCreateLocker(resource);
await semaSlim.WaitAsync();
response = someObject.DoSomething();
resourceLocker.ReleaseLocker(resource);
});
I assume that this is not possible - question is why?
It's possible, it just hasn't been done yet.
Currently, the only async-compatible synchronization primitive in the BCL is SemaphoreSlim
, which can act as a semaphore or a simple mutual-exclusion lock.
I have a basic AsyncMonitor
that I wrote, loosely based on Stephen Toub's blog post series. Note that the semantics are slightly different than the BCL Monitor
; in particular, it does not permit recursive locks (for reasons I describe on my blog).
Some synchronization primitives that .Net supplies are managed wrappers around the underlying native objects.
Currently, there are no native synchronization primitives that implement asynchronous locking. So the .Net implementers have to implement that from scratch, which is not so simple as it seems.
Also, the Windows kernel does not provide any feature of "locking-delegation", meaning you can't lock a lock in one thread, and pass the ownership to another thread, that makes the job of implementing such locks extremely difficult.
In my opinion, the third reason is more philosophical one - if you don't want to block - use non - blocking techniques, like using asynchronous IO, lock free algorithms and data structures. If the bottleneck of your application is heavy contention and the locking overhead around it, you can re-design your application in different form without having to need asynchronous locks.
I guess the problem is that by calling Monitor.Enter
the current thread wants to gain the lock for the passed object. So you should ask yourself how you would implement a Monitor.EnterAsync
? First naive attempt would be:
public async Task EnterAsync(object o)
{
await Task.Run(() => Monitor.Enter(o));
}
But that obviously does not do what you expect, because the lock would be gained by the thread started for that new Task
and not by the calling thread.
You would now need a mechanism to ensure that you can gain the lock after the await. But I currently can't think of a way how to ensure that this will work and that no other thread will gain the lock in between.
These are just my 2 cents (would have posted as comment if it wasn't too long). I'm looking forward to a more enlighting answer for you from someone with more detailed knowledge.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With