使用 async/await 时处理节流/速率限制(429 错误)

2024-01-04

我有以下异步代码,从我的项目中的很多地方调用:

public async Task<HttpResponseMessage> MakeRequestAsync(HttpRequestMessage request)
{            
    var client = new HttpClient();
    return await client.SendAsync(request).ConfigureAwait(false);                
}

如何调用上述方法的示例:

 var tasks = items.Select(async i =>
            {                    
                var response = await MakeRequestAsync(i.Url);           
                //do something with response    
            });

我使用的 ZenDesk API 每分钟允许大约 200 个请求,之后我收到 429 错误。如果遇到 429 错误,我需要执行某种 Thread.sleep 操作,但是使用 async/await 时,并行线程中可能有很多请求等待处理,我不确定如何让它们全部休眠5 秒钟左右,然后再次恢复。

解决这个问题的正确方法是什么?我想听到快速的解决方案以及良好设计的解决方案。


正如最近标记的那样,我不认为这是重复的。另一位 SO 发布者不需要基于时间的滑动窗口(或基于时间的节流),并且那里的答案不涵盖这种情况。仅当您想对传出请求设置硬性限制时,这才有效。

无论如何,一个准快速的解决方案是在MakeRequestAsync方法。像这样的事情:

public async Task<HttpResponseMessage> MakeRequestAsync(HttpRequestMessage request)
{            
    //Wait while the limit has been reached. 
    while(!_throttlingHelper.RequestAllowed) 
    {
      await Task.Delay(1000);
    }

    var client = new HttpClient();

    _throttlingHelper.StartRequest();
    var result = await client.SendAsync(request).ConfigureAwait(false);
    _throttlingHelper.EndRequest();

    return result; 
}

班上ThrottlingHelper这只是我现在制作的东西,所以你可能需要对其进行一些调试(阅读 - 可能无法开箱即用)。 它试图成为一个时间戳滑动窗口。

public class ThrottlingHelper : IDisposable
{
    //Holds time stamps for all started requests
    private readonly List<long> _requestsTx;
    private readonly ReaderWriterLockSlim _lock;

    private readonly int _maxLimit;
    private TimeSpan _interval;

    public ThrottlingHelper(int maxLimit, TimeSpan interval)
    {
        _requestsTx = new List<long>();
        _maxLimit = maxLimit;
        _interval = interval;
        _lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
    }

    public bool RequestAllowed
    {
        get
        {
            _lock.EnterReadLock();
            try
            {
                var nowTx = DateTime.Now.Ticks;
                return _requestsTx.Count(tx => nowTx - tx < _interval.Ticks) < _maxLimit;
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }
    }

    public void StartRequest()
    {
        _lock.EnterWriteLock();
        try
        {
            _requestsTx.Add(DateTime.Now.Ticks);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void EndRequest()
    {
        _lock.EnterWriteLock();
        try
        {
            var nowTx = DateTime.Now.Ticks;
            _requestsTx.RemoveAll(tx => nowTx - tx >= _interval.Ticks);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void Dispose()
    {
        _lock.Dispose();
    }
}

您可以将它用作发出请求的类中的成员,并像这样实例化它:

_throttlingHelper = new ThrottlingHelper(200, TimeSpan.FromMinutes(1));

用完后不要忘记将其丢弃。

有关的一些文档ThrottlingHelper:

  1. 构造函数参数是您希望能够在特定时间间隔内执行的最大请求以及时间间隔本身。因此,200 和 1 分钟意味着您希望每分钟不超过 200 个请求。
  2. 财产RequestAllowed让您知道您是否能够使用当前的限制设置执行请求。
  3. Methods StartRequest & EndRequest使用当前日期/时间注册/取消注册请求。

编辑/陷阱

正如@PhilipABarnes 所指出的,EndRequest可能会删除仍在进行中的请求。据我所知,这可能在两种情况下发生:

  1. 间隔很小,因此请求无法及时完成。
  2. 请求实际上需要比执行时间间隔更长的时间。

所提出的解决方案涉及实际匹配EndRequest打电话给StartRequest通过 GUID 或类似的方式调用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 async/await 时处理节流/速率限制(429 错误) 的相关文章

随机推荐