简体   繁体   English

Semaphore slim 可处理每个时间段的节流

[英]Semaphore slim to handle throttling per time period

I have a requirement from a client, to call their API, however, due to the throttling limit, we can only make 100 API calls in a minute.我有一个客户的要求,打电话给他们的 API,但是,由于限制,我们只能在一分钟内拨打 100 个 API 电话。 I am using SemaphoreSlim to handle that, Here is my code.我正在使用 SemaphoreSlim 来处理这个问题,这是我的代码。

 async Task<List<IRestResponse>> GetAllResponses(List<string> locationApiCalls) 
 {
     var semaphoreSlim = new SemaphoreSlim(initialCount: 100, maxCount: 100);
     var failedResponses = new ConcurrentBag<IReadOnlyCollection<IRestResponse>>();
     var passedResponses = new ConcurrentBag<IReadOnlyCollection<IRestResponse>>();

     var tasks = locationApiCalls.Select(async locationApiCall =>
     {
          await semaphoreSlim.WaitAsync();
          try
          {
              var response = await RestApi.GetResponseAsync(locationApi);
              if (response.IsSuccessful)
              {
                  passedResponses.Add((IReadOnlyCollection<IRestResponse>)response);
              }
              else
              {
                 failedResponses.Add((IReadOnlyCollection<IRestResponse>)response);
              }
           }
           finally
           {
               semaphoreSlim.Release();
           }
     });

     await Task.WhenAll(tasks);
     var passedResponsesList = passedResponses.SelectMany(x => x).ToList();
}

However this line然而这条线

var passedResponsesList = passedResponses.SelectMany(x => x).ToList();

never gets executed and I see Lots of failedResponses as well, I guess I have to add Task.Delay (for 1 minute) somewhere in the code as well.永远不会被执行,我也看到很多 failedResponses,我想我也必须在代码中的某处添加 Task.Delay(1 分钟)。

You need to keep track of the time when each of the previous 100 requests was executed.您需要跟踪前 100 个请求中每个请求的执行时间。 In the sample implementation below, the ConcurrentQueue<TimeSpan> records the relative completion time of each of these previous 100 requests.在下面的示例实现中, ConcurrentQueue<TimeSpan>记录了前 100 个请求中每个请求的相对完成时间。 By dequeuing the first (and hence earliest) time from this queue, you can check how much time has passed since 100 requests ago.通过从该队列中取出第一个(因此也是最早的)时间,您可以检查自 100 个请求之前已经过去了多少时间。 If it's been less than a minute, then the next request needs to wait for the remainder of the minute before it can be executed.如果不到一分钟,则下一个请求需要等待该分钟的剩余时间才能执行。

async Task<List<IRestResponse>> GetAllResponses(List<string> locationApiCalls)
{
    var semaphoreSlim = new SemaphoreSlim(initialCount: 100, maxCount: 100);
    var total = 0;
    var stopwatch = Stopwatch.StartNew();
    var completionTimes = new ConcurrentQueue<TimeSpan>();

    var failedResponses = new ConcurrentBag<IReadOnlyCollection<IRestResponse>>();
    var passedResponses = new ConcurrentBag<IReadOnlyCollection<IRestResponse>>();

    var tasks = locationApiCalls.Select(async locationApiCall =>
    {
        await semaphoreSlim.WaitAsync();

        if (Interlocked.Increment(ref total) > 100)
        {
            completionTimes.TryDequeue(out var earliest);
            var elapsed = stopwatch.Elapsed - earliest;
            var delay = TimeSpan.FromSeconds(60) - elapsed;
            if (delay > TimeSpan.Zero)
                await Task.Delay(delay);
        }

        try
        {
            var response = await RestApi.GetResponseAsync(locationApi);
            if (response.IsSuccessful)
            {
                passedResponses.Add((IReadOnlyCollection<IRestResponse>)response);
            }
            else
            {
                failedResponses.Add((IReadOnlyCollection<IRestResponse>)response);
            }
        }
        finally
        {
            completionTimes.Enqueue(stopwatch.Elapsed);
            semaphoreSlim.Release();
        }
    });

    await Task.WhenAll(tasks);
    var passedResponsesList = passedResponses.SelectMany(x => x).ToList();
}

If you're calling this method from the UI thread of a WinForms or WPF application, remember to add ConfigureAwait(false) to its await statements.如果您从 WinForms 或 WPF 应用程序的 UI 线程调用此方法,请记住将ConfigureAwait(false)添加到其await语句。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM