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

如何使用ConcurrentQueue与lock实现C# API请求按顺序执行锁操作

两种有序锁方案乱序根因定位
  • 自定义QueuedFunctions方案问题
    核心问题是入队、抢锁、出队执行三个操作拆分导致的逻辑误解。ConcurrentQueue仅能保证入队操作本身的线程安全,无法保证「线程A先发起Execute调用 → 线程A的委托先入队」的顺序和你预期的请求提交顺序完全匹配:
    1. 并发场景下线程调度顺序不可控,后发起调用的线程可能先完成入队操作
    2. 若你单元测试中用线程ID、或闭包捕获的循环变量作为顺序判断依据,大概率是测试代码本身的逻辑错误,而非锁实现的执行顺序错误
    3. 大量线程同时抢内部锁会导致严重的上下文切换开销,是触发超时的核心原因
  • 票号排队锁方案问题
    票号逻辑本身是FIFO有序锁的标准实现,出现乱序90%以上是实现细节错误:
    1. 待执行票号更新时机错误:必须在当前委托执行完成、释放锁之前更新待执行票号,而非进入临界区时更新
    2. 唤醒逻辑错误:释放锁时只需调用Monitor.Pulse()唤醒下一个等待线程即可,调用Monitor.PulseAll()会导致大量线程被唤醒后抢锁、再判断票号不匹配重新进入等待,额外增加开销,极端场景下可能因为线程调度导致看起来乱序的假象
    3. 同样存在测试代码逻辑错误的可能性,比如用线程启动顺序代替票号生成顺序作为判断依据
可用FIFO有序锁实现

以下为标准的票号排队锁实现,兼顾有序性和并发性能:

public class QueuedLock : IDisposable
{
    private readonly object _lockObj = new();
    private long _currentTicket = 0;
    private long _nextTicket = 0;

    public void Enter()
    {
        // 原子生成票号,保证票号分配顺序和请求进入顺序一致
        long myTicket = Interlocked.Increment(ref _nextTicket);
        
        lock (_lockObj)
        {
            while (myTicket != _currentTicket)
            {
                // 票号不匹配则等待
                Monitor.Wait(_lockObj);
            }
        }
    }

    public void Exit()
    {
        lock (_lockObj)
        {
            // 先更新待执行票号,再唤醒下一个线程
            Interlocked.Increment(ref _currentTicket);
            Monitor.Pulse(_lockObj);
        }
    }

    public void Dispose()
    {
        // 此处可加等待所有排队任务完成的逻辑,按需实现
    }

    // 配合using语法糖使用的封装
    public IDisposable Lock()
    {
        Enter();
        return new Releaser(this);
    }

    private struct Releaser : IDisposable
    {
        private readonly QueuedLock _owner;
        public Releaser(QueuedLock owner) => _owner = owner;
        public void Dispose() => _owner.Exit();
    }
}

使用示例

private static readonly QueuedLock _queuedLock = new();

public T ProcessData<T>(Func<T> func)
{
    using (_queuedLock.Lock())
    {
        return func();
    }
}
高并发场景优化建议
  • 若排队等待过长触发超时,可在Enter方法中增加超时判断:调用Monitor.Wait(_lockObj, 超时时间),超时后抛出异常或返回降级结果,避免请求堆积
  • 若临界区执行逻辑较短,可替换为自旋等待替代Monitor.Wait,减少线程切换开销,进一步提升性能
  • 单元测试时需在生成票号/入队时就记录顺序标识,写入委托的执行日志中,禁止用线程ID、线程启动顺序作为执行顺序的判断依据

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 20:15:01