通过一些变量限制 HttpClient 请求

Throttling HttpClient requests by some variable

我正在编写一个 class,它使用 HttpClient 来访问一个 API,我想限制可以对该 [=38= 中的某个函数进行的并发调用的数量].诀窍在于,限制是针对每个租户的,并且多个租户可能一次使用他们自己的 class 实例。

My Tenant class 只是只读上下文信息的容器。

public class Tenant
{
    public string Name { get; }
    public string ApiKey { get; }
}

这是 ApiClient:

public class ApiClient
{
    private readonly Tenant tenant;

    public ApiClient(Tenant tenant)
    {
        this.tenant = tenant;
    }

    public async Task<string> DoSomething()
    {
        var response = await this.SendCoreAsync();
        return response.ToString();
    }

    private Task<XElement> SendCore()
    {
        using (var httpClient = new HttpClient())
        {
            var httpRequest = this.BuildHttpRequest();
            var httpResponse = await httpClient.SendAsync(httpRequest);
            return XElement.Parse(await httpResponse.Content.ReadAsStringAsync());
        }
    }
}

我想做的是限制 SendCore 方法并将其限制为每个租户 两个并发请求 。我已经阅读了使用 TPLSemaphoreSlim 进行基本节流的建议(例如此处:Throttling asynchronous tasks),但我不清楚如何添加更复杂的房客。

感谢您的建议。

更新

我尝试使用 ConcurrentDictionary 中包含的一组 SemaphoreSlim 个对象(每个租户一个)。这似乎可行,但我不确定这是否理想。新代码是:

public class ApiClient
{
    private static readonly ConcurrentDictionary<string, SemaphoreSlim> Semaphores = new ConcurrentDictionary<string, SemaphoreSlim>();
    private readonly Tenant tenant;
    private readonly SemaphoreSlim semaphore;

    public ApiClient(Tenant tenant)
    {
        this.tenant = tenant;
        this.semaphore = Semaphores.GetOrAdd(this.tenant.Name, k => new SemaphoreSlim(2));
    }

    public async Task<string> DoSomething()
    {
        var response = await this.SendCoreAsync);
        return response.ToString();
    }

    private Task<XElement> SendCore()
    {
        await this.semaphore.WaitAsync();
        try
        {
            using (var httpClient = new HttpClient())
            {
                var httpRequest = this.BuildHttpRequest();
                var httpResponse = await httpClient.SendAsync(httpRequest);
                return XElement.Parse(await httpResponse.Content.ReadAsStringAsync());
            }
        }
        finally
        {
            this.semaphore.Release();
        }
    }
}

你的 SemaphoreSlim 方法对我来说似乎很合理。

一个潜在的问题是,如果 Tenants 可以在应用程序的生命周期内来来去去,那么即使 Tenants 不再存在,您也会保留信号量。

一个解决方案是使用 ConditionalWeakTable<Tenant, SemaphoreSlim> 而不是你的 ConcurrentDictionary,这确保它的键可以被垃圾收集,当它们被垃圾收集时,它会释放值。