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的错误使用:
- 每次调用GetCandles都新建HttpClientHandler和HttpClient,违反了HttpClient应全局复用的最佳实践。频繁创建销毁会耗尽Socket资源,引发连接池等待,在循环遍历coins时问题会被放大,硬件一般的PC上表现更明显。
- 用
.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
相关产品推荐
相关产品推荐

