【问题标题】:Locking with nested async calls使用嵌套异步调用锁定
【发布时间】:2013-11-18 09:53:50
【问题描述】:

我正在开发一个多线程 WindowsPhone8 应用程序,该应用程序在异步方法中具有关键部分。

有没有人知道在 C# 中正确使用信号量/互斥锁的方法,您使用的是嵌套异步调用,其中内部方法可能会获取它已经在调用堆栈中获取的相同锁?我认为 SemaphoreSlim 可能是答案,但看起来它会导致死锁。

public class Foo
{
    SemaphoreSlim _lock = new SemaphoreSlim(1);

    public async Task Bar()
    {
        await _lock.WaitAsync();

        await BarInternal();

        _lock.Release();
     }

    public async Task BarInternal()
    {
        await _lock.WaitAsync();  // deadlock

        // DO work

        _lock.Release();
     }

}

【问题讨论】:

  • 递归锁定通常被认为是一种不好的做法。难道你不能只是重组你的代码,这样就不会发生吗?
  • 在这种特殊情况下它不依赖于 async/await。这段代码在任何情况下都会陷入死锁,只是因为它一次又一次地尝试获取锁两次。是的,它们可以在不同的线程中执行(因为 async/await 是在线程池上执行的),但它们会依次执行

标签: c# windows-phone-8 async-await semaphore


【解决方案1】:

递归锁是really bad idea(IMO;链接是我自己的博客)。对于async 代码,尤其是如此。很难让async兼容的递归锁工作。我有一个proof-of-concept here 但公平的警告:我确实建议在生产中使用此代码,此代码将滚动到 AsyncEx,它是 彻底测试。

您应该做的是按照@svick 的说明重构您的代码。像这样的:

public async Task Bar()
{
    await _lock.WaitAsync();

    await BarInternal_UnderLock();

    _lock.Release();
}

public async Task BarInternal()
{
    await _lock.WaitAsync();

    await BarInternal_UnderLock();

    _lock.Release();
}

private async Task BarInternal_UnderLock()
{
    // DO work
}

【讨论】:

  • 嗯,'Bar' 和 'BarInternal' 完全相等。
  • @PoulBak:就像原始代码一样。据推测,现实世界中的Bar 会做与BarInternal 不同的事情,但由于问题中的代码相同,因此我的答案中的代码匹配。
【解决方案2】:

这是我在这种情况下所做的(不过,我对任务没有经验,所以不要打败我 ;-)
所以基本上你已经将实际实现转移到非锁定方法,并在所有获取锁定的方法中使用它们。

public class Foo
{
    SemaphoreSlim _lock = new SemaphoreSlim(1);

    public async Task Bar()
    {
        await _lock.WaitAsync();
        await BarNoLock();
        _lock.Release();
     }

    public async Task BarInternal()
    {
        await _lock.WaitAsync(); // no deadlock
        await BarNoLock();
        _lock.Release();
     }

     private async Task BarNoLock()
     {
         // do the work
     }
}

【讨论】:

    【解决方案3】:

    首先,通读 Stephen Cleary 的博文,他在回答中链接到了该博文。他提到了与递归锁(更不用说递归异步锁)相关的多种原因,例如不确定的锁状态和不一致的不变量。如果你能做到他和 Knickedi 在他们的回答中描述的重构,那就太好了。

    但是,在某些情况下,这种类型的重构是不可能的。幸运的是,现在有多个库支持嵌套的 async 调用(锁重入)。这里有两个。第一个的作者有一个blog post,在那里他会更多地谈论它。

    您可以将其合并到您的代码中(使用本示例中的第一个库):

    public class Foo
    {
        AsyncLock _lock = new AsyncLock();
    
        public async Task Bar()
        {
               // This first LockAsync() call should not block
               using (await _lock.LockAsync())
               {
                   await BarInternal();
               }
         }
    
        public async Task BarInternal()
        {
               // This second call to LockAsync() will be recognized
               // as being a reëntrant call and go through
               using (await _lock.LockAsync()) // no deadlock
               {
                   // do work
               }
         }
    }
    

    【讨论】:

    • 我同意应该仔细检查现有的嵌套锁是必要的还是只是代码路径的便利。也就是说,我强烈推荐您提到的 Flettu.AsyncLock 类。它干净简单,他使用框架原生的AsyncLocal<T>,并通过异步流传递状态,就像ThreadLocal 可以为同步代码做的那样。
    【解决方案4】:

    您可以使用System.Threading.ReaderWriterLockSlim (doc),它有一个支持递归标志:

    ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
    
    async Task Bar()
    {
        try
        {
            _lock.EnterReadLock();
            await BarInternal();
        }
        finally
        {
            if (_lock.IsReadLockHeld)
                _lock.ExitReadLock();
        }
    }
    
    async Task BarInternal()
    {
        try
        {
            _lock.EnterReadLock();
            await Task.Delay(1000);
        }
        finally
        {
            if (_lock.IsReadLockHeld)
                _lock.ExitReadLock();
        }
    }
    

    您仍然应该非常小心递归,因为很难控制哪个线程以及何时获得锁。

    问题中的代码将导致死锁,因为它会尝试两次获取锁,例如:

    await _lock.WaitAsync();
    await _lock.WaitAsync(); --> Will result in exception.
    

    虽然在SupportsRecursion 中标记ReaderWriterLockSlim 不会为这个奇怪的代码抛出异常:

     _lock.EnterReadLock();
     _lock.EnterReadLock();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-07-25
      • 1970-01-01
      • 2020-07-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-17
      相关资源
      最近更新 更多