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

异步方法未并行执行问题排查:为何三个GetOrderbook方法未同时运行?

问题根源分析

你遇到的核心问题是**MethodExecutionTimeMeasurer.Invoke方法里的同步阻塞操作彻底破坏了异步流程**,导致三个GetOrderbook调用实际是串行执行的,而非你期望的并行。

看一下Invoke<T>的实现:

public static T Invoke<T>(Func<Task<T>> action, out TimeSpan timeSpan) {
    var timer = Stopwatch.StartNew();
    var res = action.Invoke();
    res.Wait(); // 这里是关键的同步阻塞点!
    timer.Stop();
    timeSpan = timer.Elapsed;
    return res.Result;
}

当你调用_accessMethods.GetOrderbook(firstMarket)时,这个方法内部会触发Invoke,而res.Wait()会死死卡住当前线程,直到整个异步HTTP请求完全完成。这意味着第一次请求跑完60ms后,才会启动第二次请求,第三次又要等第二次跑完——所以你的时间戳才会是60ms、130ms、200ms,完全是串行累加的耗时。

你最初的理解是对的:如果GetOrderbook是真正的异步非阻塞方法,三个Task应该能同时启动,时间戳都会接近任务启动的耗时(几乎为0),最终总耗时也会接近单个请求的60ms。但现在的实现把异步方法强行拉成了同步执行。

修复方案

我们需要给测量类添加异步支持,彻底去掉同步阻塞:

  1. 给MethodExecutionTimeMeasurer新增异步版本的测量方法:
public static class MethodExecutionTimeMeasurer {
    // 原有同步方法可保留(如果其他地方还在使用)
    public static T Invoke<T>(Func<Task<T>> action, out TimeSpan timeSpan) {
        var timer = Stopwatch.StartNew();
        var res = action.Invoke();
        res.Wait();
        timer.Stop();
        timeSpan = timer.Elapsed;
        return res.Result;
    }
    public static void Invoke(Action action, out TimeSpan timeSpan) {
        var timer = Stopwatch.StartNew();
        action.Invoke();
        timer.Stop();
        timeSpan = timer.Elapsed;
    }

    // 新增异步测量方法
    public static async Task<T> InvokeAsync<T>(Func<Task<T>> action, out TimeSpan timeSpan) {
        var timer = Stopwatch.StartNew();
        var res = await action().ConfigureAwait(false);
        timer.Stop();
        timeSpan = timer.Elapsed;
        return res;
    }
}
  1. 修改GetOrderbook方法,改用异步测量逻辑:
public async Task<GetMarketResponse> GetOrderbook(string market = "USD") {
    var address = AddressBook._orderbook + market;
    var response = await MethodExecutionTimeMeasurer.InvokeAsync(
        () => _client.ExecuteGetAsyncPublic<GetMarketResponse>(address), 
        out timespan
    );
    _logger.LogInformation(string.Format("OrderBook requested for [{0}], response message: {1}. Time[ms]:{2}", address, response.Status, timespan));
    return response;
}
  1. 现在你的GetOneCombination就能真正并行执行三个请求了:
private async void GetOneCombination(string firstMarket, string secondMarket, string thirdMarket, decimal amountOfFirstCurrency) {
    Stopwatch sw = new Stopwatch();
    sw.Start();
    // 同时启动三个异步任务,不会阻塞当前线程
    Task<GetMarketResponse> result = _accessMethods.GetOrderbook(firstMarket);
    Console.WriteLine(sw.ElapsedMilliseconds); // 接近0ms(仅任务启动耗时)
    Task<GetMarketResponse> result1 = _accessMethods.GetOrderbook(secondMarket);
    Console.WriteLine(sw.ElapsedMilliseconds); // 依然接近0ms
    Task<GetMarketResponse> result2 = _accessMethods.GetOrderbook(thirdMarket);
    Console.WriteLine(sw.ElapsedMilliseconds); // 还是接近0ms
    // 等待所有任务完成
    var getMarketResponses = await Task.WhenAll(result, result1, result2);
    // 总耗时会接近单个请求的60ms左右
    Console.WriteLine($"Total elapsed: {sw.ElapsedMilliseconds}ms");
}
额外提醒
  • async void通常只适合事件处理程序,如果这个方法不是事件回调,建议改成async Task,这样调用者可以等待它执行完成,避免潜在的意外问题。
  • ConfigureAwait(false)在不需要上下文的场景下使用,可以减少不必要的线程切换,提升异步代码的性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 03:32:41