.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
相关产品推荐
相关产品推荐

