异步HTTP请求代码耗时超同步?我的C#异步实现是否有误?
你的异步代码为啥比同步还慢?问题出在这几个地方
看起来你踩了异步编程里的几个常见坑,导致你的“异步”代码其实根本没发挥异步的优势,反而因为线程调度的额外开销,耗时和同步相当甚至更长。咱们一步步拆解问题:
核心问题1:getDataFromApi里的同步阻塞调用
你在这个方法里用了.Result来等待GetAsync和ReadAsStringAsync的结果:
var ResultAsync = httpClient.GetAsync(URL).Result; // ... string Data = content.ReadAsStringAsync().Result;
.Result会阻塞当前线程,直到异步操作完成。这就相当于把本来应该异步的IO操作硬生生变成了同步阻塞,完全浪费了异步的优势——本来IO等待的时候线程可以去处理其他任务,现在线程被死死卡在这等结果,和同步代码没区别,还多了线程切换的开销。
核心问题2:错误的Task使用方式
你用new Task(() => ...).Start()创建任务,然后用Task.WaitAll等待所有任务完成:
var myTask = new Task(() => { getDataFromApi(currency); }); myTask.Start(); // ... Task.WaitAll(TaskList.ToArray());
new Task(...).Start()会从线程池里拿一个线程来执行你的代码,这会带来额外的线程调度开销;Task.WaitAll会阻塞当前线程,直到所有任务完成——这直接让整个异步方法变成了“同步等待一堆同步任务”,完全失去了异步非阻塞的特性。
核心问题3:HttpClient的不合理创建
每次调用getDataFromApi都新建HttpClientHandler和HttpClient,这会导致大量不必要的资源开销,甚至可能引发套接字耗尽的问题(因为每个HttpClient会占用一个套接字,频繁创建销毁会导致套接字来不及释放)。
正确的异步改造方案
咱们一步步把代码改成真正的异步:
1. 把getDataFromApi改成真正的异步方法
用await替代.Result,并且让方法返回Task:
private async Task getDataFromApiAsync(string currency) { string currencyCode = getCurrenciesDictionary[currency]; string URL = string.Format(GetConfig("Api"), currencyCode); // 推荐复用HttpClient实例,不要每次新建(可以改成静态成员,或者用IHttpClientFactory) using (HttpClient httpClient = new HttpClient()) { var result = await httpClient.GetAsync(URL); if (result.IsSuccessStatusCode) { string data = await result.Content.ReadAsStringAsync(); try { var ret = JsonConvert.DeserializeObject<RootObject>(data); getDataChart.CurrencyRate.Add(new CurrencyRate { Currency = currency, Rates = ret.dataset.data.AsEnumerable() .Select(date => double.Parse(date[1].ToString())).ToList() }); } catch (Exception ex) { WriteError($"{ex.Message} currency: {currency}\nURL: {URL}", ex.StackTrace); } } } }
2. 改造myAsyncMethod为真正的异步方法
用await Task.WhenAll替代Task.WaitAll,并且让方法返回Task<GenericClass>:
public async Task<GenericClass> myAsyncMethodAsync(string[] currencies) { try { var taskList = new List<Task>(); foreach (string currency in currencies) { // 直接添加异步任务,不需要手动Start taskList.Add(getDataFromApiAsync(currency)); } // 等待所有任务完成,不会阻塞当前线程 await Task.WhenAll(taskList); return getDataChart; } catch (Exception ex) { WriteError(ex.Message, ex.StackTrace); return null; } }
3. 控制器里的调用也要用await
控制器方法也要改成异步,这样才能真正利用异步的非阻塞特性:
[HttpPost, Route("GetCurrenciesData")] public async Task<IHttpActionResult> GetCurrenciesData(InputCurrenciesData cls) { try { var watch = Stopwatch.StartNew(); var data2 = GetBL.mySyncMethod(cls.currenciesList); watch.Stop(); string first = watch.Elapsed.ToString(); // 原同步方法耗时 watch.Restart(); var data = await GetBL.myAsyncMethodAsync(cls.currenciesList); watch.Stop(); string second = watch.Elapsed.ToString(); // 改造后的异步方法应该会快很多 return Ok(data); } catch (Exception ex) { getGeneric.WriteError(ex.Message, ex.StackTrace); return BadRequest(); } }
额外优化建议
- 复用HttpClient:不要每次新建HttpClient,推荐使用静态的HttpClient实例,或者在ASP.NET中使用
IHttpClientFactory来管理HttpClient的生命周期,避免套接字耗尽问题。 - 避免阻塞异步代码:永远不要在异步代码里用
.Result、.Wait()这类阻塞调用,这会导致线程死锁或者性能问题,要用await来等待异步操作完成。
内容的提问来源于stack exchange,提问作者O. H
相关产品推荐
相关产品推荐

