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

C#并发任务优化咨询:IIS中REST API批量处理100请求性能提升

优化REST API批量并发处理的方案

首先,你的核心问题在于用同步I/O+Task.Run的方式浪费了线程池资源——Task.Run会把同步的I/O操作放到线程池线程上执行,但线程在等待第三方服务响应时是被阻塞的,无法处理其他请求,这就是为什么你观察到只有7-8个线程在工作(线程池默认最小线程数有限,而且阻塞会导致线程池扩容很慢)。要达到2秒内处理100个请求的目标,必须转向异步I/O优先的方案,让线程在I/O等待时被释放,从而支持更高的并发量。

第一步:把PerformLogic改成异步方法

因为你的PerformLogic里主要是依赖式的I/O操作(先调用服务1,再调用服务2),完全可以用async/await重构为异步方法,这样线程不会在等待I/O时被占用:

// 重构为异步方法,返回Task<FlightInformation>
public async Task<FlightInformation> PerformLogicAsync(Request request, ...)
{
    // 1. 异步调用第一个第三方Web服务(替换你的同步调用为异步版本)
    var detail = await CallThirdPartyService1Async(request);
    
    // 2. 基于上一步结果异步调用第二个第三方Web服务
    var anotherDetail = await CallThirdPartyService2Async(detail);
    
    // 3. CPU绑定的转换计算(这里如果计算量不大,直接同步执行即可;如果计算很重,可以考虑用Task.Run,但优先保持在当前上下文)
    return TransformResult(detail, anotherDetail);
}

注意:确保调用第三方服务的方法本身是异步的(比如用HttpClient.GetAsync而不是HttpClient.Get),如果第三方服务的SDK只有同步方法,那可以用await Task.Run(() => SyncThirdPartyCall())包裹,但这是退而求其次的方案,优先用原生异步API。

第二步:重构批量处理代码,使用Task.WhenAll而非Task.WaitAll

原来的Task.WaitAll会阻塞当前线程,而await Task.WhenAll是异步等待,不会占用主线程,同时配合异步的PerformLogicAsync,能让线程池高效利用资源:

// 用LINQ生成异步任务列表
var tasks = multiFlightStatusRequest.FlightRequests
    .Select(req => PerformLogicAsync(req, lite, fetchRouteByAnyLeg))
    .ToList();

// 异步等待所有任务完成,直接得到结果数组
var flightInformations = await Task.WhenAll(tasks);

第三步:调整线程池最小线程数(辅助优化)

线程池默认的最小工作线程数可能比较低(比如和CPU核心数一致),而异步I/O场景下,我们需要更多的线程来处理并发的异步回调。可以在应用启动时(比如Global.asax的Application_Start或者ASP.NET Core的Program.cs)设置:

// 根据你的服务器配置和第三方服务的并发限制调整,比如设置为32/32
ThreadPool.SetMinThreads(32, 32);

这个设置让线程池一开始就有足够的线程处理并发请求,避免线程池扩容的延迟(线程池默认会每隔500ms才新增一个线程)。

第四步:用SemaphoreSlim控制并发度(关键)

虽然异步I/O能支持很高的并发,但第三方服务可能有QPS或并发连接的限制,直接发100个请求可能会被限流或者导致服务端压力过大。用SemaphoreSlim可以精准控制并发请求数:

// 设置允许的最大并发数,比如20(根据第三方服务的承受能力调整)
var semaphore = new SemaphoreSlim(20);

var tasks = multiFlightStatusRequest.FlightRequests
    .Select(async req => 
    {
        await semaphore.WaitAsync(); // 等待获取信号量
        try
        {
            return await PerformLogicAsync(req, lite, fetchRouteByAnyLeg);
        }
        finally
        {
            semaphore.Release(); // 释放信号量,让其他任务可以执行
        }
    })
    .ToList();

var flightInformations = await Task.WhenAll(tasks);

通过调整SemaphoreSlim的初始计数,你可以找到一个平衡点:既足够高以达到2秒的目标(比如20个并发的话,100个请求大概需要5轮,每轮300ms,总时间约1.5秒,加上计算时间应该能控制在2秒内),又不会超过第三方服务的限制。

为什么这样能解决问题?

  • 异步I/O让线程在等待第三方服务响应时被释放,线程池可以用同一个线程处理多个异步请求的回调,从而支持远高于线程数的并发量。
  • 避免了Task.Run带来的线程阻塞,原来的方式每个请求占用一个线程直到完成,而异步方式下一个线程可以处理多个请求的非阻塞阶段。
  • 精准控制并发度既保证了性能,又避免了对第三方服务的冲击。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 03:59:37