.NET Core如何限制HTTP并发请求并处理超时?
针对你的.NET Core并行HTTP请求问题,直接给解决方案和代码示例:
限制并发请求数
有三种常用方案,按需选择:
1. SemaphoreSlim 手动控制并发
用信号量限制同时运行的任务数,简单直观:
var semaphore = new SemaphoreSlim(10); // 最多10个并发请求 var tasks = urls.Select(async url => { await semaphore.WaitAsync(); try { return await httpClient.GetStringAsync(url); } finally { semaphore.Release(); // 无论成功失败都要释放信号量,避免泄漏 } }).ToArray(); var results = await Task.WhenAll(tasks);
2. .NET 6+ 用 Parallel.ForEachAsync
官方推荐的内置并发控制方式,代码更简洁:
var results = new ConcurrentBag<string>(); // 线程安全集合,无需加锁 var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 10 }; await Parallel.ForEachAsync(urls, parallelOptions, async (url, ct) => { var result = await httpClient.GetStringAsync(url, ct); results.Add(result); });
3. TPL Dataflow 处理复杂数据流
适合需要后续流水线处理的场景,比如请求→解析→存储:
var requestBlock = new ActionBlock<string>(async url => { var result = await httpClient.GetStringAsync(url); // 这里可以直接添加结果处理逻辑,比如解析、存数据库 }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 10 }); // 投递所有请求URL foreach (var url in urls) { await requestBlock.SendAsync(url); } requestBlock.Complete(); await requestBlock.Completion; // 等待所有请求处理完成
确保单个请求超时不影响其他
不能依赖HttpClient的全局超时(会影响所有请求),要给每个请求单独设置超时:
var tasks = urls.Select(async url => { using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); // 单个请求10秒超时 try { return (Url: url, Content: await httpClient.GetStringAsync(url, cts.Token), IsSuccess: true); } catch (OperationCanceledException) { return (Url: url, Content: "请求超时", IsSuccess: false); } catch (HttpRequestException ex) { return (Url: url, Content: $"请求失败: {ex.Message}", IsSuccess: false); } }).ToArray(); var results = await Task.WhenAll(tasks);
这样单个请求超时或失败只会返回对应状态,完全不会干扰其他请求执行。
重试失败请求的可行方案
用Polly库实现重试策略是.NET生态的标准做法,简单灵活:
- 先通过NuGet安装
Polly和Polly.Extensions.Http - 实现指数退避重试(避免频繁重试压垮服务器):
// 定义重试策略:最多重试3次,每次间隔翻倍 var retryPolicy = Policy .Handle<HttpRequestException>() .Or<OperationCanceledException>() // 可选:把超时也纳入重试范围 .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); var tasks = urls.Select(async url => { try { return await retryPolicy.ExecuteAsync(async () => { using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); return await httpClient.GetStringAsync(url, cts.Token); }); } catch (Exception ex) { return $"请求 {url} 最终失败: {ex.Message}"; } }).ToArray(); var results = await Task.WhenAll(tasks);
Task.WhenAll 是否为最佳方案?
Task.WhenAll适合等待所有任务完成后统一处理结果的场景,但如果你的需求是边执行边处理结果或者需要更灵活的并发控制,可以替换成以下模式:
1. Task.WhenAny 循环处理完成的任务
不用等全部请求结束,逐个处理已完成的任务:
var taskList = urls.Select(async url => { using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); try { return (Url: url, Result: await httpClient.GetStringAsync(url, cts.Token), IsSuccess: true); } catch (Exception ex) { return (Url: url, Result: ex.Message, IsSuccess: false); } }).ToList(); while (taskList.Count > 0) { var completedTask = await Task.WhenAny(taskList); taskList.Remove(completedTask); var result = await completedTask; // 立即处理结果,比如记录日志、更新状态 Console.WriteLine($"请求 {result.Url} {(result.IsSuccess ? "成功" : "失败")}: {result.Result}"); }
2. Parallel.ForEachAsync 或 TPL Dataflow
如前面提到的,这两种方式内置并发控制,同时支持边执行边处理,代码结构更清晰,适合大多数生产场景。
总结:如果只是简单等待全部结果,Task.WhenAll完全够用;如果需要并发控制、实时处理结果,Parallel.ForEachAsync或TPL Dataflow是更优选择。
内容的提问来源于stack exchange,提问作者Alan
相关产品推荐
相关产品推荐

