是否有 SemaphoreSlim 版本或其他方法可以让同一线程进入下游?

问题描述 投票:0回答:1

我正在重构旧的同步 C# 代码以使用异步库。当前的同步代码大量使用了锁。外部方法通常调用内部方法,两者都锁定相同的对象。这些通常是在基类中定义的“受保护对象”,并在基虚拟方法和调用基类的重写中锁定。对于同步代码,这是可以的,因为进入外部/覆盖方法锁的线程也可以进入内部/基方法锁。异步 /

SemaphoreSlim(1,1)
s 的情况并非如此。

我正在寻找一种可以在异步世界中使用的强大锁定机制,该机制将允许对同一锁定对象的后续下游调用,以按照同步“lock {...}”语法中的行为输入锁定。我最接近的是 semaphore slim,但它对我的需求来说限制太多。它不仅限制对其他线程的访问,还限制对请求进入内部调用的同一线程的访问。或者,有没有办法在调用内部

SemaphoreSlim.waitasync()
之前知道线程已经在信号量“内部”?

欢迎对锁定同一对象的内部/外部方法的设计结构提出质疑(我自己也质疑!),但如果是这样,请提出替代选项。我想过只使用私有

SemaphoreSlim(1,1)
,并让基类的继承者使用他们自己的私有信号量。但快速管理会变得很棘手。

同步示例:因为同一个线程正在请求进入内部和外部的锁,所以它允许它进入并且该方法可以完成。

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();
    }
}
c# asynchronous concurrency locking
1个回答
2
投票

我完全同意Servy的观点:

即使在同步代码中,通常也应该避免这样的重入(它通常更容易出错)。

这是我不久前写的关于该主题的博客文章。有点啰嗦;抱歉。

我正在寻找一种可以在异步世界中使用的强大锁定机制,该机制将允许对同一锁定对象的后续下游调用,以按照同步“lock {...}”语法中的行为输入锁定。

TL;DR:没有。

更长的答案:存在一个实现,但我不会使用“健壮”这个词。


我推荐的解决方案是重构first,使代码不再依赖于锁重入。让现有代码使用

SemaphoreSlim
(带有同步
Wait
)而不是
lock

这种重构并不是非常简单,但我喜欢使用的一种模式是将“内部”方法重构为始终在锁定下执行的

private
(或
protected
)实现方法。我强烈建议这些内部方法遵循命名约定;我倾向于使用“丑陋但在你脸上”
_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
替换为不可重入的
SemaphoreSlim

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
编写一个返回
IDisposable
的小扩展方法,然后您最终会得到看起来与旧的
using
块更相似的
lock
块,而不是具有
try
/
finally
无处不在。


不推荐的解决方案:

正如canton7所怀疑的,异步递归锁是可能的,并且我已经写了一个。然而,该代码从未被发布或得到支持,也永远不会被发布或支持。它尚未在生产中得到证实,甚至还没有经过全面测试。但从技术上来说,它确实存在。

© www.soinside.com 2019 - 2024. All rights reserved.