.NET Framework下C#批量异步调用外部API的最优方法(避限流)
.NET Framework中批量异步调用限流API的最优实现
针对你的需求,分批异步执行+固定间隔等待是最直接且可控的方案,你的初始思路方向是对的,我们可以把它封装成通用的工具方法,让代码更整洁、复用性更强,同时确保严格遵守限流规则。
核心实现思路
- 将所有API请求按指定批次大小拆分
- 异步执行每一批的所有请求(用
Task.WhenAll) - 等待当前批次完成且等待指定时长(取两者中耗时更长的那个),再发起下一批,避免触发限流
通用分批异步处理方法
下面是针对.NET Framework的实现,全程异步非阻塞,不依赖任何第三方库:
using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; public static class BatchProcessor { /// <summary> /// 分批异步处理任务,每批完成后等待指定时长再执行下一批 /// </summary> /// <typeparam name="T">任务项类型</typeparam> /// <param name="items">待处理的所有项</param> /// <param name="batchSize">每批处理的数量</param> /// <param name="processItemAsync">处理单个项的异步方法</param> /// <param name="delayBetweenBatchesMs">批与批之间的等待时长(毫秒)</param> /// <param name="cancellationToken">取消令牌,用于终止整个批量操作</param> public static async Task ProcessInBatchesAsync<T>( IEnumerable<T> items, int batchSize, Func<T, CancellationToken, Task> processItemAsync, int delayBetweenBatchesMs, CancellationToken cancellationToken = default) { var itemList = items.ToList(); for (int i = 0; i < itemList.Count; i += batchSize) { // 检查是否已取消 cancellationToken.ThrowIfCancellationRequested(); // 获取当前批次的项 var currentBatch = itemList.Skip(i).Take(batchSize); // 启动当前批次的所有异步任务 var batchTasks = currentBatch.Select(item => processItemAsync(item, cancellationToken)).ToList(); // 等待当前批次完成,同时确保至少等待指定时长(哪个耗时久等哪个) await Task.WhenAll(Task.WhenAll(batchTasks), Task.Delay(delayBetweenBatchesMs, cancellationToken)); } } }
使用示例
以你提到的100次API调用、每批20次、间隔5秒为例:
// 模拟外部API调用方法 private async Task CallExternalApiAsync(int requestId, CancellationToken cancellationToken) { // 实际调用逻辑替换为你的API请求代码 Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 发起请求 {requestId}"); await Task.Delay(150, cancellationToken); // 模拟API响应耗时 Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 请求 {requestId} 完成"); } // 执行批量调用 public async Task RunBatchCalls() { var allRequests = Enumerable.Range(1, 100); var cancellationTokenSource = new CancellationTokenSource(); try { await BatchProcessor.ProcessInBatchesAsync( items: allRequests, batchSize: 20, processItemAsync: CallExternalApiAsync, delayBetweenBatchesMs: 5000, cancellationToken: cancellationTokenSource.Token); } catch (OperationCanceledException) { Console.WriteLine("批量操作已取消"); } }
方案优势
- 严格限流:确保每批请求仅在上一批完成且等待足够时长后才发起,完全规避触发API限流的风险
- 异步高效:全程使用异步操作,不会阻塞主线程,比同步的
Parallel.ForEach性能更优 - 通用复用:封装的工具方法可用于任何需要分批异步处理的场景,不止API调用
- 可取消性:支持
CancellationToken,方便在需要时终止整个批量操作
额外优化建议
如果API的限流规则是基于时间窗口(例如每分钟最多100次),而非固定批次,你可以调整逻辑:记录每批的开始时间,计算下一批的最早启动时间,确保在时间窗口内的请求总数不超过限制。不过针对你当前的需求,上述方案已经完全足够。
内容的提问来源于stack exchange,提问作者Byte
相关产品推荐
相关产品推荐

