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

使用C#并行调用外部API时性能异常缓慢的原因排查

问题分析与优化方案

1. HttpClient重复创建导致套接字耗尽

每次调用GetAPIData都新建HttpClient实例,会快速耗尽系统套接字资源,引发请求排队、延迟飙升。.NET中HttpClient设计为可复用的单例/静态实例,频繁创建销毁会破坏HTTP连接池的复用机制,这是性能瓶颈的核心原因之一。

修复方案:
将HttpClient改为静态实例复用,避免重复创建:

// 静态初始化一次,全局复用
private static readonly HttpClient _httpClient = new HttpClient();

static async Task GetAPIData(string url, ConcurrentBag<Stats> bag)
{
    // 移除原有的var client = new HttpClient();
    _httpClient.Timeout = TimeSpan.FromMinutes(5); // 避免设置无限超时,合理配置超时时间
    var bearerToken = "xxxxxxxxxxxxxx";
    
    // 仅在首次调用时添加Authorization头,避免重复添加报错
    if (!_httpClient.DefaultRequestHeaders.Contains("Authorization"))
    {
        _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {bearerToken}");
    }

    // 后续业务逻辑保持不变,将client替换为_httpClient
}

2. ConcurrentBag高并发锁竞争

三个任务同时对ConcurrentBag执行Add操作,虽然它是线程安全的,但每次Add都涉及内部锁竞争。当数据量达到1万+时,频繁的锁竞争会大幅拖慢执行速度。

修复方案:
每个任务先将数据收集到本地普通列表(无锁操作),完成后一次性批量添加到ConcurrentBag,减少锁竞争次数:

static async Task GetAPIData(string url, ConcurrentBag<Stats> bag)
{
    var localList = new List<Stats>(); // 本地临时列表,避免跨线程锁竞争

    using (var stream = await _httpClient.GetStreamAsync(url).ConfigureAwait(false))
    using (var sr = new StreamReader(stream))
    using (JsonTextReader reader = new JsonTextReader(sr))
    {
        reader.SupportMultipleContent = true;
        var serializer = new JsonSerializer();
        while (reader.Read())
        {
            if (reader.TokenType == JsonToken.StartObject)
            {
                var stats = serializer.Deserialize<Stats?>(reader);
                if (stats != null)
                {
                    localList.Add(stats); // 先写入本地列表
                }
            }
        }
    }

    // 批量写入ConcurrentBag,仅触发少量锁操作
    foreach (var item in localList)
    {
        bag.Add(item);
    }
}

3. 多余的Task.Run包裹异步方法

GetAPIData本身是异步方法,返回Task,使用Task.Run(() => GetAPIData(...))会额外占用线程池线程——异步IO操作本身不会阻塞线程,完全没必要多此一举,反而会增加线程调度开销。

修复方案:
直接调用异步方法,无需Task.Run包裹:

var url = "https://extrenalsite/api/stream";
var bag = ConcurrentBag<Stats>();

var task1 = GetAPIData(url, bag);
var task2 = GetAPIData(url, bag);
var task3 = GetAPIData(url, bag);

await Task.WhenAll(task1, task2, task3);

4. 潜在的API服务端限流

Postman单次请求耗时30秒,但并行3次调用时,API服务端可能对同一IP的并发请求做了限流(比如限制并发数、降低响应优先级),导致每个请求的响应时间大幅增加。

验证与处理:
先单独运行一个任务,查看耗时是否和Postman接近。如果单个任务耗时正常,多任务变慢,基本可以确定是服务端限流。此时需要减少并行数,或联系API提供者调整限流策略。

5. Json反序列化效率优化

使用JsonTextReader逐个反序列化对象的方式,在数据量较大时效率偏低。如果API返回的是标准JSON数组,可直接反序列化为列表,避免逐行读取的开销:

using (var response = await _httpClient.GetAsync(url).ConfigureAwait(false))
{
    response.EnsureSuccessStatusCode();
    var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
    var statsList = JsonConvert.DeserializeObject<List<Stats>>(json); // 使用Newtonsoft.Json
    if (statsList != null)
    {
        foreach (var item in statsList)
        {
            bag.Add(item);
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 15:45:23