You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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生态的标准做法,简单灵活:

  1. 先通过NuGet安装Polly和Polly.Extensions.Http
  2. 实现指数退避重试(避免频繁重试压垮服务器):
// 定义重试策略:最多重试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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:57:07