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

.NET Core 6如何实现按CODE粒度条件加锁 支持同CODE单线程异步处理

适用实现机制
  • 核心选型用键粒度异步锁,禁止使用lock/Monitor等线程亲和型锁——这类锁无法适配async/await的线程切换逻辑,且全局锁会阻塞所有CODE的请求,不符合不同CODE并行执行的需求。底层基于SemaphoreSlim实现单个CODE的互斥,它原生提供WaitAsync()方法,不会阻塞线程,完美适配异步编程模型。
  • 用ConcurrentDictionary<int, LockEntry>存储每个CODE对应的锁实例,每个锁条目包含:控制互斥的SemaphoreSlim、用于强制终止的CancellationTokenSource、记录当前排队/持锁任务数的引用计数。当引用计数归零时自动删除字典中对应的锁条目,避免内存泄漏。
  • 强制终止能力通过绑定到锁条目的取消令牌实现:需要终止指定CODE的执行时,直接触发对应CancellationTokenSource的取消信号,持锁任务的所有计算、IO步骤都要监听该令牌,收到信号后立即中断执行、释放锁。
  • 所有对锁条目的计数修改、字典增删操作都用原子方法保证线程安全,适配外部工厂独立创建线程/任务的场景,避免出现同CODE对应多个锁实例、计数错乱的问题。
可直接复用的.NET 6 代码模板
// 单个CODE对应的锁条目
internal sealed class CodeLockEntry : IDisposable
{
    public SemaphoreSlim Semaphore { get; } = new SemaphoreSlim(1, 1);
    public CancellationTokenSource RunningCts { get; set; } = new CancellationTokenSource();
    // 引用计数:当前持锁+排队等待同CODE的任务总数
    public int WaiterCount = 0;

    public void Dispose()
    {
        Semaphore.Dispose();
        RunningCts.Dispose();
    }
}

public sealed class CodeAsyncLockManager
{
    // 全局单例,可通过DI注册为Singleton生命周期
    public static readonly CodeAsyncLockManager Instance = new CodeAsyncLockManager();
    private readonly ConcurrentDictionary<int, CodeLockEntry> _lockMap = new();

    /// <summary>
    /// 异步等待进入指定CODE的锁,返回可释放的锁句柄
    /// </summary>
    /// <param name="code">整型业务键</param>
    /// <param name="externalCancellationToken">外部传入的取消令牌(比如请求超时、用户主动终止)</param>
    /// <returns>锁句柄,Dispose时自动释放锁</returns>
    public async Task<IDisposable> WaitAsync(int code, CancellationToken externalCancellationToken = default)
    {
        // 原子获取/新增锁条目,引用计数+1
        var entry = _lockMap.GetOrAdd(code, _ => new CodeLockEntry());
        Interlocked.Increment(ref entry.WaiterCount);

        try
        {
            // 绑定外部取消和锁自身的终止令牌
            using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(externalCancellationToken, entry.RunningCts.Token);
            // 异步等待锁,不阻塞线程
            await entry.Semaphore.WaitAsync(linkedCts.Token).ConfigureAwait(false);

            return new CodeLockReleaser(this, code, entry);
        }
        catch
        {
            // 等待过程中被取消/报错,递减计数,必要时清理条目
            CleanupEntryIfNeeded(code, entry);
            throw;
        }
    }

    /// <summary>
    /// 强制终止指定CODE当前正在执行的持锁任务
    /// </summary>
    /// <param name="code">要终止的业务键</param>
    public void TerminateRunningTask(int code)
    {
        if (_lockMap.TryGetValue(code, out var entry))
        {
            if (!entry.RunningCts.IsCancellationRequested)
            {
                entry.RunningCts.Cancel();
            }
        }
    }

    private void CleanupEntryIfNeeded(int code, CodeLockEntry entry)
    {
        if (Interlocked.Decrement(ref entry.WaiterCount) == 0)
        {
            // 没有任何等待/持锁的任务,从字典移除条目释放资源
            if (_lockMap.TryRemove(code, out var removed) && removed == entry)
            {
                entry.Dispose();
            }
        }
    }

    // 锁释放句柄,实现IDisposable支持using语法自动释放
    private sealed class CodeLockReleaser : IDisposable
    {
        private readonly CodeAsyncLockManager _owner;
        private readonly int _code;
        private readonly CodeLockEntry _entry;
        private bool _disposed;

        public CodeLockReleaser(CodeAsyncLockManager owner, int code, CodeLockEntry entry)
        {
            _owner = owner;
            _code = code;
            _entry = entry;
        }

        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;

            // 释放信号量
            if (_entry.Semaphore.CurrentCount == 0)
            {
                _entry.Semaphore.Release();
            }

            // 重置已触发取消的令牌,供后续同CODE任务使用
            if (_entry.RunningCts.IsCancellationRequested)
            {
                _entry.RunningCts.Dispose();
                _entry.RunningCts = new CancellationTokenSource();
            }

            // 清理计数和无用条目
            _owner.CleanupEntryIfNeeded(_code, _entry);
        }
    }
}
使用示例
// 业务逻辑调用
public async Task ProcessCodeAsync(int code, CancellationToken requestAbortToken)
{
    // 等待对应CODE的锁,不同CODE完全不互斥
    using var lockHandle = await CodeAsyncLockManager.Instance.WaitAsync(code, requestAbortToken);
    // 关联终止令牌,保证强制终止能立刻中断所有执行步骤
    var lockEntry = CodeAsyncLockManager.Instance.GetLockEntryForTest(code); // 实际使用可暴露内部entry的令牌属性
    using var execCts = CancellationTokenSource.CreateLinkedTokenSource(requestAbortToken, lockEntry.RunningCts.Token);
    
    try
    {
        // 所有耗时操作必须传入取消令牌
        await HeavyComputeAsync(code, execCts.Token).ConfigureAwait(false);
        await DbOperationAsync(code, execCts.Token).ConfigureAwait(false);
    }
    catch (OperationCanceledException)
    {
        // 处理被终止后的回滚逻辑
        await RollbackAsync(code);
    }
}

// 强制终止指定CODE的执行
public void AbortCodeProcess(int code)
{
    CodeAsyncLockManager.Instance.TerminateRunningTask(code);
}
实现注意点
  • 所有异步等待尽量加上ConfigureAwait(false),避免同步上下文死锁,适配ASP.NET Core等异步服务场景
  • 业务逻辑中所有耗时操作(计算、IO、数据库请求)必须传入关联后的取消令牌,否则强制终止无法即时生效,会等任务自然跑完才释放锁
  • 不要在持有锁的逻辑中启动不感知取消令牌的后台任务,否则终止时会出现资源泄漏、数据不一致问题
  • 引用计数的增减必须用Interlocked原子操作,避免多线程下计数错误导致锁条目被提前释放或者内存泄漏

内容的提问来源于stack exchange,提问作者user18928007

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 07:36:33