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

基于customerNo限制API并行调用:异步场景Monitor使用异常问题

问题分析与解决方案

问题背景

需求:针对查询参数customerNo,同一参数值的API调用不允许并行执行,不同参数值的调用需支持并行。
使用await client.GetAsync(QueryHelpers.AddQueryString(path, parameters))进行异步API调用时,尝试用System.Threading.Monitor.TryEnter加锁,释放锁时抛出异常:

System.Threading.SynchronizationLockException: Object synchronization method was called from an unsynchronized block of code.

相关代码片段:

try
{
    Monitor.TryEnter(lockObj, timeout, ref lockTaken);
    if (lockTaken)
    {
        List<PatientData> patients = await RetrievePatientsDataAsync(client, new Dictionary<string, string>
        {
            ["customerNo"] = customerNo,
            ["offset"] = _lindeAPIOptions.Offset,
            ["pagesize"] = _lindeAPIOptions.Pagesize
        });

        data.Patients = patients;
        return data;
    }
}
finally
{
    // Ensure that the lock is released.
    if (lockTaken)
    {
        Monitor.Exit(lockObj);
    }
}

其中RetrievePatientsDataAsync内部包含上述异步API调用。

异常原因

Monitor是线程绑定的同步原语:获取锁的线程必须是释放锁的线程。但异步代码中,await会导致线程切换——执行await前的代码在一个线程,await完成后可能在另一个线程继续执行。这就导致Monitor.Exit时,当前线程并非获取锁的线程,触发SynchronizationLockException。

解决方案:基于SemaphoreSlim的按参数锁

我们需要一个异步友好的锁机制,并且能按customerNo区分锁。可以用Dictionary<string, SemaphoreSlim>来为每个customerNo维护独立的信号量,实现同一customerNo串行、不同customerNo并行的效果。

步骤1:定义全局的信号量字典

private static readonly Dictionary<string, SemaphoreSlim> _customerLocks = new Dictionary<string, SemaphoreSlim>();
private static readonly object _dictionaryLock = new object();

_dictionaryLock用于保护对_customerLocks的并发访问,避免字典操作的线程安全问题。

步骤2:修改业务逻辑代码

// 获取当前customerNo对应的信号量
SemaphoreSlim semaphore;
lock (_dictionaryLock)
{
    if (!_customerLocks.TryGetValue(customerNo, out semaphore))
    {
        semaphore = new SemaphoreSlim(1, 1); // 初始计数1,最大计数1,实现互斥
        _customerLocks.Add(customerNo, semaphore);
    }
}

try
{
    await semaphore.WaitAsync(timeout); // 异步等待获取锁
    List<PatientData> patients = await RetrievePatientsDataAsync(client, new Dictionary<string, string>
    {
        ["customerNo"] = customerNo,
        ["offset"] = _lindeAPIOptions.Offset,
        ["pagesize"] = _lindeAPIOptions.Pagesize
    });

    data.Patients = patients;
    return data;
}
finally
{
    semaphore.Release(); // 释放信号量
    
    // 可选:清理长时间未使用的信号量,避免内存泄漏
    lock (_dictionaryLock)
    {
        if (semaphore.CurrentCount == 1) // 当前信号量未被占用
        {
            _customerLocks.Remove(customerNo);
            semaphore.Dispose();
        }
    }
}

方案说明

  • SemaphoreSlim.WaitAsync是异步方法,支持await,不会阻塞线程,且不绑定特定线程,完美适配异步场景。
  • 每个customerNo对应一个独立的SemaphoreSlim,保证同一customerNo的调用串行执行,不同customerNo的调用互不干扰,支持并行。
  • 可选的清理逻辑:当信号量回到未占用状态(CurrentCount == 1)时,从字典中移除并释放,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 11:01:11