【问题标题】:Why is there no Monitor.EnterAsync-like method为什么没有类似 Monitor.EnterAsync 的方法
【发布时间】:2016-07-01 08:26:28
【问题描述】:

我在新框架中看到了许多方法,这些方法在 C# 中使用对 async/await 的新异步模式/语言支持。为什么没有Monitor.EnterAsync() 或其他async lock 机制释放当前线程并在lock 可用时立即返回?

我认为这是不可能的——问题是为什么?

【问题讨论】:

  • 我知道这个关于监视器的问题,但是有一些同步原语确实提供异步操作:msdn.microsoft.com/en-us/library/hh462723.aspx
  • 绝对有可能!它只是很难编写(并且更难正确使用),并且它没有被改装到现有的Monitor 类中,因为Monitor 是一个相当基本的类型,这是一个相当复杂的案例(假设你真的,真的需要它)。有关实现,请参阅 github.com/StephenCleary/AsyncEx/wiki/AsyncMonitor
  • 可以实现,但是当您尝试并实际使用它时会出错。 stackoverflow.com/questions/7612602/…
  • 其他大问题:整个Monitor.Enter 被构建为最快的锁定原语。 Task 的开销相当大……两者混合得不太好。
  • 有这样一种方法,在那种同步对象上,不关心哪个线程拥有锁。忽略这些细节会让你进入带有 async/await 的大狗。使用 SemaphoreSlim.WaitAsync()

标签: c#


【解决方案1】:

我猜问题是通过调用Monitor.Enter 当前线程 想要获得传递对象的锁。所以你应该问问自己你将如何实现Monitor.EnterAsync?第一次天真的尝试是:

public async Task EnterAsync(object o)
{
    await Task.Run(() => Monitor.Enter(o));
}

但这显然不会达到您的预期,因为锁定将由为该新 Task 启动的线程获得,而不是由调用线程获得。
您现在需要一种机制来确保您可以在等待之后获得锁。但是我目前想不出一种方法来确保这将起作用并且没有其他线程会在两者之间获得锁定。


这些只是我的 2 美分(如果不是太长,会作为评论发布)。期待有更详细知识的人为您提供更有启发性的答案。

【讨论】:

    【解决方案2】:

    .Net 提供的一些同步原语是围绕底层本机对象的托管包装器。

    目前,没有实现异步锁定的本机同步原语。所以.Net 实现者必须从头开始实现它,这并不像看起来那么简单。

    另外,Windows 内核没有提供任何“locking-delegation”的特性,这意味着你不能在一个线程中锁定一个锁,并将所有权传递给另一个线程,这使得实现这种锁的工作变得非常困难.

    在我看来,第三个原因更哲学 - 如果你不想阻塞 - 使用非阻塞技术,如使用异步 IO、无锁算法和数据结构。如果您的应用程序的瓶颈是激烈的争用和围绕它的锁定开销,您可以以不同的形式重新设计您的应用程序,而无需异步锁。

    【讨论】:

    • Monitor is a wrapper around CRITICAL_SECTION 不正确。能不能加点支持的权威资源?
    • Joe duffy 在 windows book 上的并发编程指出了这一点从物理上讲,监视器不包括 Windows CRITICAL_SECTION,但它的行为就像它一样。 Pgno:272
    【解决方案3】:

    虽然 .NET 默认情况下没有异步监视器,但Stephen Cleary 有一个很棒的库 AsyncEx 可以在使用 async/await 时处理同步问题。

    它有一个 AsyncMonitor 类,它几乎完全符合您的要求。您可以从GitHubNuGet package 获得它。

    用法示例:

    var monitor = new AsyncMonitor();
    using (await monitor.EnterAsync())
    {
        // Critical section
    }
    

    【讨论】:

      【解决方案4】:

      我认为这是不可能的——问题是为什么?

      有可能,只是还没有完成。

      目前,BCL 中唯一兼容异步的同步原语是SemaphoreSlim,它可以充当信号量或简单的互斥锁。

      我有一个基本的AsyncMonitor that I wrote,大致基于Stephen Toub's blog post series。请注意,语义与 BCL Monitor 略有不同;特别是,它不允许递归锁(对于reasons I describe on my blog)。

      【讨论】:

      • System.Threading.Monitor.Enterobject 作为参数并且是static。从资源有限的角度来看,这很有用。 AsyncMonitor 没有此功能,因此我假设您必须为每个要锁定的对象实例化一个 AsyncMonitor。使用数千个这样安全吗?几万?
      • @LittleEndian: Monitor.Enter 很奇怪,因为在 .NET 中 every object 是一个监视器。这是一个非常奇怪的设计决定,但事实就是如此。大多数人遵循为锁定定义显式object 的约定,这既是为了代码的清晰性和正确性(一些较旧的类型将lock(this)),所以Monitor 的一般用法是分配一个单独的object 以供使用与显示器。所以你定义一个AsyncMonitor 来覆盖你想要锁定在一起的每一组对象。使用数以万计是安全的,但我强烈质疑任何需要这么多的设计。
      • “我强烈质疑任何需要这么多的设计。”我对这种观点感到惊讶。我认为多线程和细粒度锁定将齐头并进。如果我正在处理一万个对象的集合,很自然地锁定整个集合或单个项目。
      • @LittleEndian:如果队列的入口和出口都有锁,为什么每个对象都需要自己的锁?
      【解决方案5】:

      这对我很有效,如下所述: SemaphoreSlim Class


      信号量有两种类型:本地信号量命名系统信号量

      前者是应用本地的。后者在整个操作系统中都是可见的,适用于进程间同步。

      SemaphoreSlim 是不使用 Windows 内核信号量的 Semaphore 类的轻量级替代方案。与 Semaphore 类不同,SemaphoreSlim 类不支持命名系统信号量。

      您只能将其用作本地信号量。 SemaphoreSlim 类是推荐用于在单个应用中同步的信号量。

      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
            }
       }
      

      用法

      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);
         }     
      

      异步

         Task.Run(async ()=>{
              var semaSlim = resourceLocker.GetOrCreateLocker(resource);
      
              await semaSlim.WaitAsync();     
      
              response = someObject.DoSomething();
      
              resourceLocker.ReleaseLocker(resource);
          });
      

      【讨论】:

        猜你喜欢
        • 2019-08-14
        • 1970-01-01
        • 1970-01-01
        • 2011-07-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-23
        • 2012-01-04
        相关资源
        最近更新 更多