Simple way to rate limit HttpClient requests

后端 未结 3 1321
终归单人心
终归单人心 2020-12-28 21:26

I am using the HTTPClient in System.Net.Http to make requests against an API. The API is limited to 10 requests per second.

My code is roughly like so:



        
3条回答
  •  粉色の甜心
    2020-12-28 22:19

    The API is limited to 10 requests per second.

    Then just have your code do a batch of 10 requests, ensuring they take at least one second:

    Items[] items = ...;
    
    int index = 0;
    while (index < items.Length)
    {
      var timer = Task.Delay(TimeSpan.FromSeconds(1.2)); // ".2" to make sure
      var tasks = items.Skip(index).Take(10).Select(i => ProcessItemsAsync(i));
      var tasksAndTimer = tasks.Concat(new[] { timer });
      await Task.WhenAll(tasksAndTimer);
      index += 10;
    }
    

    Update

    My ProcessItems method makes 1-4 API calls depending on the item.

    In this case, batching is not an appropriate solution. You need to limit an asynchronous method to a certain number, which implies a SemaphoreSlim. The tricky part is that you want to allow more calls over time.

    I haven't tried this code, but the general idea I would go with is to have a periodic function that releases the semaphore up to 10 times. So, something like this:

    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(10);
    
    private async Task ThrottledSendRequestAsync(HttpRequestMessage request, CancellationToken token)
    {
      await _semaphore.WaitAsync(token);
      return await SendRequestAsync(request, token);
    }
    
    private async Task PeriodicallyReleaseAsync(Task stop)
    {
      while (true)
      {
        var timer = Task.Delay(TimeSpan.FromSeconds(1.2));
    
        if (await Task.WhenAny(timer, stop) == stop)
          return;
    
        // Release the semaphore at most 10 times.
        for (int i = 0; i != 10; ++i)
        {
          try
          {
            _semaphore.Release();
          }
          catch (SemaphoreFullException)
          {
            break;
          }
        }
      }
    }
    

    Usage:

    // Start the periodic task, with a signal that we can use to stop it.
    var stop = new TaskCompletionSource();
    var periodicTask = PeriodicallyReleaseAsync(stop.Task);
    
    // Wait for all item processing.
    await Task.WhenAll(taskList);
    
    // Stop the periodic task.
    stop.SetResult(null);
    await periodicTask;
    
        

    提交回复
    热议问题