正如最近标记的那样,我不认为这是重复的。另一位 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
:
- 构造函数参数是您希望能够在特定时间间隔内执行的最大请求以及时间间隔本身。因此,200 和 1 分钟意味着您希望每分钟不超过 200 个请求。
- 财产
RequestAllowed
让您知道您是否能够使用当前的限制设置执行请求。
- Methods
StartRequest
& EndRequest
使用当前日期/时间注册/取消注册请求。
编辑/陷阱
正如@PhilipABarnes 所指出的,EndRequest
可能会删除仍在进行中的请求。据我所知,这可能在两种情况下发生:
- 间隔很小,因此请求无法及时完成。
- 请求实际上需要比执行时间间隔更长的时间。
所提出的解决方案涉及实际匹配EndRequest
打电话给StartRequest
通过 GUID 或类似的方式调用。