You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

是否存在支持同线程重入的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 12:57:01