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

异步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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:19:56