是否存在支持同线程重入的SemaphoreSlim变体或同类异步锁方案?
解决方案
方案1:实现支持异步执行流的可重入异步锁
异步场景下不能通过线程ID判断锁持有状态(await 执行后线程可能发生切换),可以通过 AsyncLocal<T> 跟踪异步执行流的锁重入计数,封装兼容重入逻辑的异步锁,完全匹配同步 lock 的行为:
public class ReentrantAsyncLock { private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly AsyncLocal<int> _reentrancyCount = new AsyncLocal<int>(); public async Task<IDisposable> WaitAsync(CancellationToken cancellationToken = default) { if (_reentrancyCount.Value > 0) { _reentrancyCount.Value++; return new Releaser(this, false); } await _semaphore.WaitAsync(cancellationToken); _reentrancyCount.Value = 1; return new Releaser(this, true); } private void Release(bool isTopLevel) { if (isTopLevel) { _reentrancyCount.Value = 0; _semaphore.Release(); } else { _reentrancyCount.Value--; } } private readonly struct Releaser : IDisposable { private readonly ReentrantAsyncLock _owner; private readonly bool _isTopLevel; public Releaser(ReentrantAsyncLock owner, bool isTopLevel) { _owner = owner; _isTopLevel = isTopLevel; } public void Dispose() { _owner.Release(_isTopLevel); } } }
使用方式和原生 SemaphoreSlim 基本一致,天然支持嵌套重入:
protected ReentrantAsyncLock LockObjectAsync = new ReentrantAsyncLock(); public async Task OuterAsync() { using (await LockObjectAsync.WaitAsync()) { foreach (var item in collection) { await InnerAsync(item); } } } public async Task InnerAsync(string item) { using (await LockObjectAsync.WaitAsync()) { DoWork(item); } }
方案2:优化嵌套锁的设计结构(更推荐)
嵌套加锁的设计本身就存在潜在死锁风险,可重入锁只是兼容旧代码的权宜之计,从结构上避免嵌套锁请求,性能和可维护性更优:
拆分公开入口和内部无锁实现
遵循「公开方法负责加锁、内部核心逻辑无锁」的约定,完全避免嵌套加锁需求:
protected SemaphoreSlim LockObjectAsync = new SemaphoreSlim(1,1); // 公开外层方法,统一加锁 public async Task OuterAsync() { try { await LockObjectAsync.WaitAsync(); foreach (var item in collection) { // 直接调用无锁内部实现,无需二次加锁 await InnerCoreAsync(item); } } finally { LockObjectAsync.Release(); } } // 单独暴露的内层公开方法,自行负责加锁 public async Task InnerAsync(string item) { try { await LockObjectAsync.WaitAsync(); await InnerCoreAsync(item); } finally { LockObjectAsync.Release(); } } // 无锁核心逻辑,供同锁上下文的内部方法调用 private Task InnerCoreAsync(string item) { return DoWorkAsync(item); }
基类和子类重写时也可以沿用该约定,重写逻辑直接调用基类的Core方法即可,无需重复加锁。
拆分锁粒度
如果基类和子类的保护资源完全独立,可以各自使用私有 SemaphoreSlim 做锁,避免跨继承层级的锁冲突,虽然短期管理成本稍高,但锁边界更清晰,大幅降低后续迭代出现死锁的概率。
内容的提问来源于stack exchange,提问作者Adam Diament
相关产品推荐
相关产品推荐

