C# 信号量slim可处理每个时间段的限制
我有一个客户要求调用他们的API,但是,由于节流限制,我们一分钟只能调用100个API。我使用SemaphoreSlim来处理这个问题,下面是我的代码C# 信号量slim可处理每个时间段的限制,c#,semaphore,throttling,C#,Semaphore,Throttling,我有一个客户要求调用他们的API,但是,由于节流限制,我们一分钟只能调用100个API。我使用SemaphoreSlim来处理这个问题,下面是我的代码 async Task<List<IRestResponse>> GetAllResponses(List<string> locationApiCalls) { var semaphoreSlim = new SemaphoreSlim(initialCount: 100, maxCount:
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();
}
从不执行,我也看到很多失败的响应,我想我必须在代码中的某个地方添加Task.Delay(延迟1分钟)。您需要跟踪前100个请求的执行时间。在下面的示例实现中,
ConcurrentQueue
记录了前100个请求中每个请求的相对完成时间。通过从该队列中第一次(因此也是最早的)退出队列,您可以检查自100个请求之后经过了多少时间。如果不到一分钟,则下一个请求需要等待一分钟的剩余时间才能执行
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();
}
异步任务GetAllResponses(列表位置Apicalls)
{
var semaphoreSlim=新的信号量lim(initialCount:100,maxCount:100);
var合计=0;
var stopwatch=stopwatch.StartNew();
var completionTimes=新的ConcurrentQueue();
var failedResponses=新的ConcurrentBag();
var passedResponses=new ConcurrentBag();
var tasks=locationApiCalls.Select(异步locationApiCalls=>
{
等待信号量lim.WaitAsync();
如果(联锁增量(参考总数)>100)
{
completionTimes.TryDequeue(最早退出变量);
var已用=秒表已用-最早;
var delay=从秒(60)开始的时间跨度-已用时间;
如果(延迟>时间跨度为零)
等待任务。延迟(延迟);
}
尝试
{
var response=await RestApi.GetResponseAsync(locationApi);
if(response.issucessful)
{
passedResponses.Add((IReadOnlyCollection)响应);
}
其他的
{
failedResponses.Add((IReadOnlyCollection)响应);
}
}
最后
{
completionTimes.Enqueue(秒表运行时间);
semaphoreSlim.Release();
}
});
等待任务。何时(任务);
var passedResponsesList=passedResponses.SelectMany(x=>x.ToList();
}
如果您是从WinForms或WPF应用程序的UI线程调用此方法,请记住将
ConfigureAwait(false)
添加到其await
语句中。您需要跟踪前100个请求执行的时间。在下面的示例实现中,ConcurrentQueue
记录了前100个请求中每个请求的相对完成时间。通过从该队列中第一次(因此也是最早的)退出队列,您可以检查自100个请求之后经过了多少时间。如果不到一分钟,则下一个请求需要等待一分钟的剩余时间才能执行
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();
}
异步任务GetAllResponses(列表位置Apicalls)
{
var semaphoreSlim=新的信号量lim(initialCount:100,maxCount:100);
var合计=0;
var stopwatch=stopwatch.StartNew();
var completionTimes=新的ConcurrentQueue();
var failedResponses=新的ConcurrentBag();
var passedResponses=new ConcurrentBag();
var tasks=locationApiCalls.Select(异步locationApiCalls=>
{
等待信号量lim.WaitAsync();
如果(联锁增量(参考总数)>100)
{
completionTimes.TryDequeue(最早退出变量);
var已用=秒表已用-最早;
var delay=从秒(60)开始的时间跨度-已用时间;
如果(延迟>时间跨度为零)
等待任务。延迟(延迟);
}
尝试
{
var response=await RestApi.GetResponseAsync(locationApi);
if(response.issucessful)
{
passedResponses.Add((IReadOnlyCollection)响应);
}
其他的
{
failedResponses.Add((IReadOnlyCollection)响应);
}
}
最后
{
completionTimes.Enqueue(秒表运行时间);
semaphoreSlim.Release();
}
});
等待任务。何时(任务);
var passedResponsesList=passedResponses.SelectMany(x=>x.ToList();
}
如果您是从WinForms或WPF应用程序的UI线程调用此方法,请记住将
ConfigureAwait(false)
添加到其await
语句中。将查询限制为100并发与将查询限制为100每分钟不同。如果没有合适的方法,我看不到调试代码的方法,但即使有可能解决未完成或失败的任务,您仍然会遇到一个问题,即您似乎没有正确实施基于时间的限制。您的应用程序一次运行多少个实例?这是一个控制台应用程序吗?Web app?@mjwills,这是一个控制台应用程序。它一次只运行一个实例。您可以使用答案中的RateLimiter
类。附带说明,ConcurrentBag
不适合此用法。ConcurrentQueue
会更好地为您服务,因为它保留了插入项目的顺序。ConcurrentBag
是一个类。它不是线程安全的列表
。将查询限制为100并发与将查询限制为100每分钟不同。如果没有合适的方法,我看不到调试代码的方法,但即使有可能解决未完成或失败的任务,您仍然会遇到一个问题,即您似乎没有正确实施基于时间的限制。您的应用程序一次运行多少个实例?这是一个控制台应用程序吗?Web app?@mjwills,这是一个控制台应用程序。它一次只运行一个实例。您可以使用答案中的RateLimiter
类。附带说明,ConcurrentBag
不适合此用法。ConcurrentQueue<