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

.NET Framework 4.7下HttpClient并发请求失效问题求助

问题原因分析与解决办法

核心问题根源

你的HttpClient实现速率上不去,核心是错误混用异步API与同步阻塞调用,同时用了不适合IO密集型任务的Parallel.ForEach,导致HttpClient的异步优势完全丧失:

  1. 同步阻塞破坏异步特性:两种HttpClient实现中都用了.Result阻塞等待异步操作完成,让异步请求退化为串行执行——每个请求必须等前一个完成后才能继续,直接把速率限制在1/s。
  2. Parallel.ForEach不适合IO密集场景:Parallel.ForEach是为CPU密集型任务设计的,HTTP请求属于IO密集型,强行用它启动多线程+阻塞调用,会导致线程池调度混乱,无法发挥HttpClient连接池的复用优势。
  3. 连接限制配置未生效:虽然设置了ServicePointManager的连接数,但阻塞调用让这些配置无法作用于实际的并发请求。

修正后的正确实现

下面是完全异步、高效控制并发的HttpClient实现,能充分利用第三方API的并发限制:

public HttpClient HttpClient { get; set; } // 注意:HttpClient必须全局复用(单例),禁止每次请求创建新实例

private readonly SemaphoreSlim _semaphore;
private int _userThreads = 7;

// 构造函数初始化信号量
public YourServiceClassName()
{
    _semaphore = new SemaphoreSlim(_userThreads);
}

public async Task<List<ApiGetOutcome>> GetGameInfo(int systemID, List<string> romNames)
{
    // 此处初始化parameters、urlList的逻辑保持不变
    List<ApiGetOutcome> apiGetOutcomes = new List<ApiGetOutcome>();
    var syncLock = new object(); // 用于多线程安全添加结果

    // 确保连接限制生效
    ServicePointManager.DefaultConnectionLimit = _userThreads;
    ServicePointManager.FindServicePoint(new Uri(ApiParameters.HostAddress)).ConnectionLimit = _userThreads;

    Stopwatch sw = Stopwatch.StartNew();

    // 异步并发核心逻辑:信号量控制并发数 + 全异步调用
    var tasks = urlList.Select(async url =>
    {
        await _semaphore.WaitAsync();
        try
        {
            // 全异步调用,无阻塞
            string json = await HttpClient.GetStringAsync(url);
            
            // 线程安全地添加结果到集合
            lock (syncLock)
            {
                apiGetOutcomes.Add(new ApiGetOutcome { Data = json, Url = url });
                Debug.WriteLine($"Rate: {apiGetOutcomes.Count / sw.Elapsed.TotalSeconds}");
                Debug.WriteLine(apiGetOutcomes.Count);
            }
        }
        finally
        {
            _semaphore.Release();
        }
    }).ToList();

    // 等待所有异步任务完成
    await Task.WhenAll(tasks);

    return apiGetOutcomes;
}

关键修正点说明

  • 移除所有.Result阻塞调用:改用await实现真正的异步非阻塞,让HttpClient的连接池可以同时处理多个请求,充分利用并发限制。
  • 用SemaphoreSlim控制并发:替代Parallel.ForEach,专门针对IO密集型任务的并发控制,避免线程池资源浪费。
  • 线程安全的集合操作:添加lock确保多异步任务同时修改apiGetOutcomes时不会出现线程安全问题。
  • 复用HttpClient实例:HttpClient设计为可复用的单例,频繁创建会导致连接泄漏和性能下降,务必保证全局复用。

对原有实现的补充分析

  • Approach1(Parallel.ForEach + HttpClient):Parallel启动多线程,但每个线程被.Result阻塞,线程池被无效占用,HttpClient无法并发处理请求,最终速率被限制。
  • Approach2(Semaphore + 阻塞调用):信号量本身没问题,但.Result让每个任务串行执行,信号量的并发控制完全失效。
  • Approach3(Parallel.ForEach + WebClient):每个WebClient是独立实例,同步调用占用独立线程,Parallel的多线程能实现一定并发,但这种方式效率低(线程资源浪费),远不如异步HttpClient的性能。

内容的提问来源于stack exchange,提问作者stigzler

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 01:04:51