不允许基于参数的并行 API 调用

Don't allow parallel API invocation based on parameter

提问人:GIovanni 提问时间:11/14/2023 更新时间:11/15/2023 访问量:52

问:

基于单个查询参数,我要求一次不允许对 API 进行多次调用。必须允许使用不同值的查询参数进行并行调用。

我通过

await client.GetAsync(QueryHelpers.AddQueryString(path, parameters))

因此,有一个异步调用不允许我使用 System.Threading.Monitor.TryEnter beacause 当我尝试释放锁时出现异常:

System.Threading.SynchronizationLockException:从不同步的代码块调用对象同步方法。

这是代码片段

            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);
                }

            }

参数为 customerNo。在方法 RetrievePatientsDataAsync 中,我调用上述

await client.GetAsync(QueryHelpers.AddQueryString(path, parameters))
C# REST 异步 锁定 system.net

评论

1赞 Sinatr 11/14/2023
部分请参阅此副本和异常,解释了为什么会出现异常。lock
1赞 JonasH 11/14/2023
您说不允许并行调用,但是并发调用的预期行为是什么?一个人优雅地失败了?一个等待另一个完成?并且您是否希望允许与不同的客户进行并发呼叫否?不同客户的数量有限制吗?
0赞 GIovanni 11/14/2023
@JonasH,如果与同一客户发生突发冲突,呼叫可能会正常失败。问题是 API 非常慢,并且经常由于我无法了解的某种原因而失败。我想它不能承受太大的负载。因此,要求是通过避免对 customerNo 的并行访问来限制访问。

答:

1赞 JonasH 11/14/2023 #1

您可以保留所有“锁定”的 ID 列表,如下所示:

private HashSet<long> lockedIds = new();

public async Task ExclusiveAccessById(long id)
{
    bool hasExclusiveAccess = true;
    lock (lockedIds)
    {
        hasExclusiveAccess = lockedIds.Add(id);
    }
    if (!hasExclusiveAccess)
    {
        // handle failure
        return;
    }

    try
    {
        // Handle success
        await Task.Delay(1);
        return;
    }
    finally
    {
        lock (lockedIds)
        {
            lockedIds.Remove(id);
        }
    }
}

这里的关键部分是在实际方法运行时或执行任何等待等操作时不要按住锁。您可能可以对 ConcurrentDictionary 执行类似操作,但如果争用率较低,则锁的开销应该很低。

评论

1赞 JonasH 11/14/2023
@TheodorZoulias Oups,你的权利,检查应该在尝试/最终之前。
0赞 GIovanni 11/15/2023
比你。我已经实现了一个包含 HashSet 的线程安全单例,然后它就可以工作了。