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

C# HttpClient请求响应过慢:是否与客户端硬件相关?

问题分析与解决方案

问题描述

我在C#中使用HttpClient对象从服务器获取数据,编写了HttpRequest类的GetCandles方法用于发送请求(代码如下)。本地运行时响应正常,获取数据耗时不足1秒,但在另一台客户端PC上,HTTP响应耗时极长(5秒至5分钟不等)。请问该问题是否与系统硬件有关?

原代码

HttpRequest类的GetCandles方法

public CML.Candle GetCandles(CML.Coins Coin, int candleCount, string timeFrame)
{
    HttpClientHandler clientHandler = new HttpClientHandler();
    clientHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
    clientHandler.Properties.Add("Accept-Encoding", "br, gzip, deflate");
    clientHandler.CookieContainer = new CookieContainer();

    _client = new HttpClient(clientHandler);

    _client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml");
    _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    _client.DefaultRequestHeaders.Add("User-Agent", "just test app");
    _client.DefaultRequestHeaders.Add("Connection", "keep-alive");

    CML.Candle candles = new CML.Candle();
    CandleResponse candleResponse = new CandleResponse();
    int tmpCandlesCount = candleCount;
    int step = 1500;
    do
    {
        if (candleCount > 0)
        {
            if (tmpCandlesCount > step)
                tmpCandlesCount -= step;
            else
                step = tmpCandlesCount;
        }

        string queryString = GetQueryString(Coin.symbol, step, candleCount, timeFrame);

        URI = _serverApi.GetCandlesRoute(queryString);
        HttpResponseMessage response = new HttpResponseMessage();
        response = _client.GetAsync(URI).Result;

        string jsonData = response.Content.ReadAsStringAsync().Result;
        candleResponse = JsonConvert.DeserializeObject<CandleResponse>(jsonData);

        candles = (Convert2Candle(candleResponse.data, Coin));
    } while (candleResponse.data.Count == 1500);


    return candles;
}

调用该方法的ProcessData方法

void ProcessData(object obj)
{
    try
    {
        _isInProccess = true;
        ExKu.HttpRequests requests = new ExKu.HttpRequests();
        CML.CandleQueryInfo queryInfo = (CML.CandleQueryInfo)obj;
        CML.Candle candle = new CML.Candle();
        CML.CandleOpration opration = new CML.CandleOpration();
        List<CML.Coins> coins = _coins.Where(x => x.enableTrading = true && x.quoteCurrency == _selectedMarket).OrderBy(x => x.symbol).ToList(); //&& x.symbol == "BTC-USDT"

        foreach (var coin in coins)
        {
            _canceltoken.ThrowIfCancellationRequested();
            _symbol = coin.symbol;
            
            
            candle = requests.GetCandles(coin, queryInfo.candleCount, queryInfo.timeFrame);
            

            if (candle != null && candle.Candles.Count >= (queryInfo.candleCount * 0.80))
            {

                Task MA20 = new Task(() =>
                {
                    candle.Candles = opration.CalculateMovingAverage(candle.Candles, 20);
                });


                Task MA50 = new Task(() =>
                {
                    candle.Candles = opration.CalculateMovingAverage(candle.Candles, 50);
                });

                Task MA100 = new Task(() =>
                {
                    candle.Candles = opration.CalculateMovingAverage(candle.Candles, 100);
                });

                Task MA200 = new Task(() =>
                {
                    candle.Candles = opration.CalculateMovingAverage(candle.Candles, 200);
                });

                MA20.Start();
                MA50.Start();
                MA100.Start();
                MA200.Start();

                Task.WaitAll(MA20, MA50, MA100, MA200);

                Task RSI = new Task(() =>
                {
                    candle.Candles = opration.CalculateRSI(candle.Candles, 14);
                });

                Task CCI = new Task(() =>
                {
                    candle.Candles = opration.CalculateCCI(candle.Candles, 20);
                });

                Task ichimoku = new Task(() =>
                {
                    candle.Candles = opration.CalculateIchimoku(candle.Candles);
                });

                Task MACD = new Task(() =>
                {
                    candle.Candles = opration.CalcMACD(candle.Candles);
                });


                RSI.Start();
                CCI.Start();
                ichimoku.Start();
                MACD.Start();
                Task.WaitAll(RSI, CCI, ichimoku, MACD);

                Task bollingerband = new Task(() =>
                {
                    candle.Candles = opration.CalculateBollingerBand(candle.Candles, 20, 1);
                });

                bollingerband.Start();
                Task.WaitAll(bollingerband);

                _candles.Add(candle);
            }

            var delFinish = new FinishProcData(FinishProcess);
            dataGridView1.Dispatcher.Invoke(delFinish, candle, 0);
        }

        var endProc = new FinishProcData(FinishProcess);
        dataGridView1.Dispatcher.Invoke(endProc, candle, 1);
    }
    catch (Exception ex)
    {
        var error = new ShowError(DisplayException);
        this.Dispatcher.Invoke(error, ex, "ProcessData");
    }
}

问题结论

硬件因素大概率不是核心原因,更多问题出在网络环境差异、代码实现缺陷或者目标PC的系统配置上,具体分析如下:

  • 网络环境差异:目标PC的网络带宽、延迟、路由节点和本地不同,比如处于弱网、跨运营商环境,或者防火墙/代理拦截请求,导致TCP握手、数据传输耗时剧增。可以直接在目标PC用浏览器或curl访问API地址,先排除网络本身的问题。
  • HttpClient的错误使用:
    1. 每次调用GetCandles都新建HttpClientHandler和HttpClient,违反了HttpClient应全局复用的最佳实践。频繁创建销毁会耗尽Socket资源,引发连接池等待,在循环遍历coins时问题会被放大,硬件一般的PC上表现更明显。
    2. 用.Result同步阻塞异步方法,会导致线程阻塞,在资源不足的环境下容易引发线程池饥饿,进一步拉长响应时间。
  • 系统配置与软件冲突:目标PC的防火墙、杀毒软件可能对HTTP请求做深度扫描,增加处理耗时;或者.NET版本和本地不同,旧版本的HttpClient在压缩处理等方面效率较低。
  • 硬件影响(可能性低):只有当目标PC的CPU、内存严重不足,导致线程调度、网络IO队列处理缓慢时,才会间接影响请求耗时,但这种情况通常伴随整个系统卡顿,而非仅HTTP请求变慢。

代码优化建议

1. 全局复用HttpClient

将HttpClient实例改为静态成员,避免每次请求新建:

private static readonly HttpClient _client;

static HttpRequest()
{
    HttpClientHandler clientHandler = new HttpClientHandler();
    clientHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
    
    _client = new HttpClient(clientHandler);
    _client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml");
    _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    _client.DefaultRequestHeaders.Add("User-Agent", "just test app");
    _client.DefaultRequestHeaders.Add("Connection", "keep-alive");
    _client.DefaultRequestHeaders.Add("Accept-Encoding", "br, gzip, deflate"); // 正确添加编码头
}

2. 改用异步/await替代同步阻塞

将GetCandles改为异步方法,避免线程阻塞:

public async Task<CML.Candle> GetCandlesAsync(CML.Coins Coin, int candleCount, string timeFrame)
{
    CML.Candle candles = new CML.Candle();
    CandleResponse candleResponse = new CandleResponse();
    int tmpCandlesCount = candleCount;
    int step = 1500;
    do
    {
        if (candleCount > 0)
        {
            if (tmpCandlesCount > step)
                tmpCandlesCount -= step;
            else
                step = tmpCandlesCount;
        }

        string queryString = GetQueryString(Coin.symbol, step, candleCount, timeFrame);
        var URI = _serverApi.GetCandlesRoute(queryString);
        
        HttpResponseMessage response = await _client.GetAsync(URI);
        response.EnsureSuccessStatusCode(); // 确保请求成功
        string jsonData = await response.Content.ReadAsStringAsync();
        candleResponse = JsonConvert.DeserializeObject<CandleResponse>(jsonData);

        // 修正逻辑:累加蜡烛数据(原代码每次覆盖,可能是错误)
        var newCandles = Convert2Candle(candleResponse.data, Coin);
        candles.Candles.AddRange(newCandles.Candles);
    } while (candleResponse.data.Count == 1500);

    return candles;
}

3. 修复多线程竞争问题

原代码中多个Task同时修改candle.Candles,存在线程安全问题,改为串行处理或使用线程安全集合:

async void ProcessData(object obj)
{
    try
    {
        _isInProccess = true;
        ExKu.HttpRequests requests = new ExKu.HttpRequests();
        CML.CandleQueryInfo queryInfo = (CML.CandleQueryInfo)obj;
        CML.CandleOpration opration = new CML.CandleOpration();
        List<CML.Coins> coins = _coins.Where(x => x.enableTrading == true && x.quoteCurrency == _selectedMarket)
                                      .OrderBy(x => x.symbol)
                                      .ToList();

        foreach (var coin in coins)
        {
            _canceltoken.ThrowIfCancellationRequested();
            _symbol = coin.symbol;
            
            // 调用异步方法
            var candle = await requests.GetCandlesAsync(coin, queryInfo.candleCount, queryInfo.timeFrame);

            if (candle != null && candle.Candles.Count >= (queryInfo.candleCount * 0.80))
            {
                // 串行处理指标计算,避免线程竞争
                candle.Candles = opration.CalculateMovingAverage(candle.Candles, 20);
                candle.Candles = opration.CalculateMovingAverage(candle.Candles, 50);
                candle.Candles = opration.CalculateMovingAverage(candle.Candles, 100);
                candle.Candles = opration.CalculateMovingAverage(candle.Candles, 200);

                candle.Candles = opration.CalculateRSI(candle.Candles, 14);
                candle.Candles = opration.CalculateCCI(candle.Candles, 20);
                candle.Candles = opration.CalculateIchimoku(candle.Candles);
                candle.Candles = opration.CalcMACD(candle.Candles);

                candle.Candles = opration.CalculateBollingerBand(candle.Candles, 20, 1);

                _candles.Add(candle);
            }

            var delFinish = new FinishProcData(FinishProcess);
            dataGridView1.Dispatcher.Invoke(delFinish, candle, 0);
        }

        var endProc = new FinishProcData(FinishProcess);
        dataGridView1.Dispatcher.Invoke(endProc, candle, 1);
    }
    catch (Exception ex)
    {
        var error = new ShowError(DisplayException);
        this.Dispatcher.Invoke(error, ex, "ProcessData");
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 21:25:01