使用嵌套异步调用进行锁定

时间:2013-11-06 22:36:13

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

我正在开发一个多线程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 个答案:

答案 0 :(得分:12)

递归锁是really bad idea(IMO;链接到我自己的博客)。对于async代码,这是尤其 true。获得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
}

答案 1 :(得分:7)

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

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

答案 2 :(得分:2)

首先,通读 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
           }
     }
}

答案 3 :(得分:-1)

您可以使用具有支持递归标志的$ geom part list da0 | awk '/Mediasize:/ { print (++flag==1)?$2:"" } /[ ]type:/ { print (flag==1)?$2:"" }' 61872754688 freebsd-ufs doc):

System.Threading.ReaderWriterLockSlim

仍然要非常小心递归,因为很难控制哪个线程获取了锁定以及何时锁定。

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

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. 中标记ReaderWriterLockSlim时,此奇怪的代码不会引发异常:

SupportsRecursion