我有以下异步代码,可以从我项目中的很多地方调用:
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秒左右,然后再次恢复。
解决此问题的正确方法是什么?我想听听快速解决方案以及良好的设计解决方案。
答案 0 :(得分: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
的一些文档:
RequestAllowed
可让您知道是否能够使用当前限制设置执行请求。 StartRequest
&amp; EndRequest
使用当前日期/时间注册/取消注册请求。 修改/陷阱强>
如@PhilipABarnes所示,EndRequest
可能会删除仍在进行中的请求。据我所知,这可能发生在两种情况:
建议的解决方案涉及通过GUID或类似方式实际匹配EndRequest
次StartRequest
次来电。
答案 1 :(得分:0)
如果有多个请求在while循环中等待RequestAllowed,其中一些请求可能同时启动。如何简单的StartRequestIfAllowed?
public class ThrottlingHelper : DisposeBase
{
//Holds time stamps for all started requests
private readonly List<long> _requestsTx;
private readonly Mutex _mutex = new Mutex();
private readonly int _maxLimit;
private readonly TimeSpan _interval;
public ThrottlingHelper(int maxLimit, TimeSpan interval)
{
_requestsTx = new List<long>();
_maxLimit = maxLimit;
_interval = interval;
}
public bool StartRequestIfAllowed
{
get
{
_mutex.WaitOne();
try
{
var nowTx = DateTime.Now.Ticks;
if (_requestsTx.Count(tx => nowTx - tx < _interval.Ticks) < _maxLimit)
{
_requestsTx.Add(DateTime.Now.Ticks);
return true;
}
else
{
return false;
}
}
finally
{
_mutex.ReleaseMutex();
}
}
}
public void EndRequest()
{
_mutex.WaitOne();
try
{
var nowTx = DateTime.Now.Ticks;
_requestsTx.RemoveAll(tx => nowTx - tx >= _interval.Ticks);
}
finally
{
_mutex.ReleaseMutex();
}
}
protected override void DisposeResources()
{
_mutex.Dispose();
}
}