C# 使用嵌套异步调用锁定
我正在开发一个多线程WindowsPhone8应用程序,它在异步方法中具有关键部分 有人知道在C#中正确使用信号量/互斥量的方法吗?在C#中,您使用嵌套异步调用,其中内部方法可能获取的锁与其在调用堆栈中已经获取的锁相同?我认为信号量lim可能是答案,但它似乎导致了死锁C# 使用嵌套异步调用锁定,c#,windows-phone-8,async-await,semaphore,C#,Windows Phone 8,Async Await,Semaphore,我正在开发一个多线程WindowsPhone8应用程序,它在异步方法中具有关键部分 有人知道在C#中正确使用信号量/互斥量的方法吗?在C#中,您使用嵌套异步调用,其中内部方法可能获取的锁与其在调用堆栈中已经获取的锁相同?我认为信号量lim可能是答案,但它似乎导致了死锁 public class Foo { SemaphoreSlim _lock = new SemaphoreSlim(1); public async Task Bar() { await
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();
}
}
以下是我在这种情况下所做的(尽管如此,我对任务没有经验,所以不要打败我;-)
因此,基本上,您已经将实际实现转移到非锁定方法,并在所有获取锁的方法中使用这些方法
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
}
}
递归锁是一个(IMO;链接到我自己的博客)。这对于async
代码尤其如此。很难让异步
兼容的递归锁工作。我有一个合理的警告:我不建议在生产中使用此代码,此代码将not滚动到AsyncEx中,并且它将not经过彻底测试
相反,您应该做的是按照@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
}
您可以使用具有支持递归标志的
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.
在SupportsRecursion
中标记ReaderWriterLockSlim
时,不会引发此奇怪代码的异常:
_lock.EnterReadLock();
_lock.EnterReadLock();
首先,阅读斯蒂芬·克利里(Stephen Cleary)的博文,他在回答中链接了这篇博文。他提到了多种原因,例如不确定的锁状态和不一致的不变量,它们与递归锁(更不用说递归异步锁)有关。如果你能像他和尼克迪在回答中描述的那样进行重构,那就太好了 然而,在某些情况下,这种重构是不可能的。幸运的是,现在有多个库支持嵌套异步调用(锁重入)。这里有两个。第一本书的作者有一本书,他在书中更多地谈到了这一点
递归锁定通常被认为是一种不好的做法。难道你不能重新构造你的代码,这样就不会发生这种情况吗?在这种特殊情况下,它不依赖于async/await。这段代码在任何情况下都会陷入死锁,因为它一次又一次地尝试获取锁。是的,它们可以在不同的线程中执行(因为async/await是在线程池中执行的),但它们是按顺序执行的。呃,“Bar”和“BarInternal”完全相同。@PoulBak:就像原始代码一样。据推测,一个真实世界的
条
会做一些与BarInternal
不同的事情,但由于问题中的代码是相同的,所以我的答案中的代码是匹配的。我同意应该仔细检查现有的嵌套锁是必要的还是只是代码路径的便利。也就是说,我强烈推荐您提到的Flettu.AsyncLock类。它干净、简单,他使用的是框架固有的,并通过异步流传递状态,就像ThreadLocal
对同步代码所做的那样。
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
}
}
}