是否有一个版本的 Semaphore Slim 或其他方法可以让同一个线程进入下游?
Is there a version of Semaphore Slim or another method that will let the same thread in downstream?
我正在重构旧的同步 C# 代码以使用异步库。当前的同步代码自由使用锁。外部方法通常调用内部方法,两者都锁定相同的对象。这些通常是在基 class 中定义的“受保护对象”,并锁定在基虚拟方法和调用基的覆盖中。对于同步代码,没关系,因为进入 outer/override 方法锁的线程也可以进入 inner/base 方法一。 async / SemaphoreSlim(1,1)s 不是这种情况。
我正在寻找一种可以在异步世界中使用的强大的锁定机制,它将允许对同一锁定对象的后续下游调用进入锁定,按照同步“锁定{... }“ 句法。我最接近的是 semaphore slim,但它对我的需求来说太严格了。它不仅限制对其他线程的访问,而且也限制对请求进入内部调用的同一线程的访问。或者,有没有办法在调用内部 SemaphoreSlim.waitasync()?
之前知道线程已经在信号量“内部”
欢迎对同时锁定在同一个对象上的 inner/outer 方法的设计结构提出质疑的答案(我自己质疑!),但如果是这样,请提出替代方案。我想过只使用私有的 SemaphoreSlim(1,1)s,并让基础 class 的继承者使用他们自己的私有信号量。但要快速管理起来会很棘手。
同步示例:因为同一个线程在内部和外部都请求进入锁,它允许它进入并且该方法可以完成。
private object LockObject = new object();
public void Outer()
{
lock (LockObject)
{
foreach (var item in collection)
{
Inner(item);
}
}
}
public void Inner(string item)
{
lock (LockObject)
{
DoWork(item);
}
}
异步示例:信号量不是那样工作的,它会卡在内部异步的第一次迭代中,因为它只是一个信号,它不会让另一个信号通过,直到它被释放,即使同一个线程请求它
protected SemaphoreSlim LockObjectAsync = new SemaphoreSlim(1,1);
public async Task OuterAsync()
{
try
{
await LockObjectAsync.WaitAsync();
foreach (var item in collection)
{
await InnerAsync(item);
}
}
finally
{
LockObjectAsync.Release();
}
}
public async Task InnerAsync(string item)
{
try
{
await LockObjectAsync.WaitAsync();
DoWork(item);
}
finally
{
LockObjectAsync.Release();
}
}
我完全同意 Servy 的观点:
Reentrancy like this should generally be avoided even in synchronous code (it usually makes it easier to make mistakes).
Here's a blog post on the subject 前阵子写的。有点啰嗦;对不起。
I'm looking for a robust locking mechanism I can use in the async world that will allow subsequent downstream calls to the same locking object, to enter the lock, as per the behaviour in synchronous "lock {...}" syntax.
TL;DR:没有。
更长的答案:存在一个实现,但我不会使用“健壮”这个词。
我推荐的解决方案是先重构,这样代码就不再依赖于锁重入。使现有代码使用 SemaphoreSlim
(同步 Wait
s)而不是 lock
.
这种重构并不是非常简单,但我喜欢使用的一种模式是将“内部”方法重构为始终执行的 private
(或 protected
,如有必要)实现方法下锁。我强烈建议这些内部方法遵循命名约定;我倾向于使用 ugly-but-in-your-face _UnderLock
。使用您的示例代码,它看起来像:
private object LockObject = new();
public void Outer()
{
lock (LockObject)
{
foreach (var item in collection)
{
Inner_UnderLock(item);
}
}
}
public void Inner(string item)
{
lock (LockObject)
{
Inner_UnderLock(item);
}
}
private void Inner_UnderLock(string item)
{
DoWork(item);
}
如果有多个锁,这会变得更加复杂,但对于简单的情况,这种重构效果很好。然后就可以把可重入的lock
s换成不可重入的SemaphoreSlim
s:
private SemaphoreSlim LockObject = new(1);
public void Outer()
{
LockObject.Wait();
try
{
foreach (var item in collection)
{
Inner_UnderLock(item);
}
}
finally
{
LockObject.Release();
}
}
public void Inner(string item)
{
LockObject.Wait();
try
{
Inner_UnderLock(item);
}
finally
{
LockObject.Release();
}
}
private void Inner_UnderLock(string item)
{
DoWork(item);
}
如果你有很多这样的方法,考虑为 SemaphoreSlim
写一个小的扩展方法 returns IDisposable
,然后你最终得到 using
块看起来更像旧的 lock
块,而不是到处都是 try
/finally
。
不推荐的方案:
正如canton7所怀疑的,异步递归锁是可能的,并且I have written one。然而,该代码从未被发布或支持,也永远不会。它尚未在生产中得到证实,甚至还没有经过全面测试。但从技术上讲,它确实存在。
我正在重构旧的同步 C# 代码以使用异步库。当前的同步代码自由使用锁。外部方法通常调用内部方法,两者都锁定相同的对象。这些通常是在基 class 中定义的“受保护对象”,并锁定在基虚拟方法和调用基的覆盖中。对于同步代码,没关系,因为进入 outer/override 方法锁的线程也可以进入 inner/base 方法一。 async / SemaphoreSlim(1,1)s 不是这种情况。
我正在寻找一种可以在异步世界中使用的强大的锁定机制,它将允许对同一锁定对象的后续下游调用进入锁定,按照同步“锁定{... }“ 句法。我最接近的是 semaphore slim,但它对我的需求来说太严格了。它不仅限制对其他线程的访问,而且也限制对请求进入内部调用的同一线程的访问。或者,有没有办法在调用内部 SemaphoreSlim.waitasync()?
之前知道线程已经在信号量“内部”欢迎对同时锁定在同一个对象上的 inner/outer 方法的设计结构提出质疑的答案(我自己质疑!),但如果是这样,请提出替代方案。我想过只使用私有的 SemaphoreSlim(1,1)s,并让基础 class 的继承者使用他们自己的私有信号量。但要快速管理起来会很棘手。
同步示例:因为同一个线程在内部和外部都请求进入锁,它允许它进入并且该方法可以完成。
private object LockObject = new object();
public void Outer()
{
lock (LockObject)
{
foreach (var item in collection)
{
Inner(item);
}
}
}
public void Inner(string item)
{
lock (LockObject)
{
DoWork(item);
}
}
异步示例:信号量不是那样工作的,它会卡在内部异步的第一次迭代中,因为它只是一个信号,它不会让另一个信号通过,直到它被释放,即使同一个线程请求它
protected SemaphoreSlim LockObjectAsync = new SemaphoreSlim(1,1);
public async Task OuterAsync()
{
try
{
await LockObjectAsync.WaitAsync();
foreach (var item in collection)
{
await InnerAsync(item);
}
}
finally
{
LockObjectAsync.Release();
}
}
public async Task InnerAsync(string item)
{
try
{
await LockObjectAsync.WaitAsync();
DoWork(item);
}
finally
{
LockObjectAsync.Release();
}
}
我完全同意 Servy 的观点:
Reentrancy like this should generally be avoided even in synchronous code (it usually makes it easier to make mistakes).
Here's a blog post on the subject 前阵子写的。有点啰嗦;对不起。
I'm looking for a robust locking mechanism I can use in the async world that will allow subsequent downstream calls to the same locking object, to enter the lock, as per the behaviour in synchronous "lock {...}" syntax.
TL;DR:没有。
更长的答案:存在一个实现,但我不会使用“健壮”这个词。
我推荐的解决方案是先重构,这样代码就不再依赖于锁重入。使现有代码使用 SemaphoreSlim
(同步 Wait
s)而不是 lock
.
这种重构并不是非常简单,但我喜欢使用的一种模式是将“内部”方法重构为始终执行的 private
(或 protected
,如有必要)实现方法下锁。我强烈建议这些内部方法遵循命名约定;我倾向于使用 ugly-but-in-your-face _UnderLock
。使用您的示例代码,它看起来像:
private object LockObject = new();
public void Outer()
{
lock (LockObject)
{
foreach (var item in collection)
{
Inner_UnderLock(item);
}
}
}
public void Inner(string item)
{
lock (LockObject)
{
Inner_UnderLock(item);
}
}
private void Inner_UnderLock(string item)
{
DoWork(item);
}
如果有多个锁,这会变得更加复杂,但对于简单的情况,这种重构效果很好。然后就可以把可重入的lock
s换成不可重入的SemaphoreSlim
s:
private SemaphoreSlim LockObject = new(1);
public void Outer()
{
LockObject.Wait();
try
{
foreach (var item in collection)
{
Inner_UnderLock(item);
}
}
finally
{
LockObject.Release();
}
}
public void Inner(string item)
{
LockObject.Wait();
try
{
Inner_UnderLock(item);
}
finally
{
LockObject.Release();
}
}
private void Inner_UnderLock(string item)
{
DoWork(item);
}
如果你有很多这样的方法,考虑为 SemaphoreSlim
写一个小的扩展方法 returns IDisposable
,然后你最终得到 using
块看起来更像旧的 lock
块,而不是到处都是 try
/finally
。
不推荐的方案:
正如canton7所怀疑的,异步递归锁是可能的,并且I have written one。然而,该代码从未被发布或支持,也永远不会。它尚未在生产中得到证实,甚至还没有经过全面测试。但从技术上讲,它确实存在。