16

我正在开发一个多线程 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();
     }

}
4

4 回答 4

14

递归锁是一个非常糟糕的主意(IMO;链接是我自己的博客)。对于代码尤其如此。async很难让async兼容的递归锁工作。我在这里有一个概念验证但公平警告:我建议在生产中使用此代码,此代码不会滚动到 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
}
于 2013-11-06T23:30:32.880 回答
6

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

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
     }
}
于 2013-11-06T23:28:35.500 回答
2

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

但是,在某些情况下,这种类型的重构是不可能的。幸运的是,现在有多个库支持嵌套异步调用(锁重入)。这里有两个。第一篇文章的作者有一篇博客文章,他在其中进行了更多讨论。

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

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
           }
     }
}
于 2021-01-08T03:19:18.513 回答
-2

您可以使用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.

虽然标记ReaderWriterLockSliminSupportsRecursion不会为这个奇怪的代码抛出异常:

 _lock.EnterReadLock();
 _lock.EnterReadLock();
于 2020-03-22T16:18:16.140 回答