Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is there no Monitor.EnterAsync-like method

Tags:

c#

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?

like image 233
pg0xC Avatar asked Mar 15 '16 18:03

pg0xC


5 Answers

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
}
like image 102
Gediminas Masaitis Avatar answered Nov 13 '22 07:11

Gediminas Masaitis


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);
    });
like image 34
dynamiclynk Avatar answered Sep 28 '22 00:09

dynamiclynk


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).

like image 6
Stephen Cleary Avatar answered Nov 13 '22 07:11

Stephen Cleary


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.

like image 5
David Haim Avatar answered Nov 13 '22 07:11

David Haim


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.

like image 5
René Vogt Avatar answered Nov 13 '22 06:11

René Vogt