异步方法未并行执行问题排查:为何三个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。但现在的实现把异步方法强行拉成了同步执行。
修复方案
我们需要给测量类添加异步支持,彻底去掉同步阻塞:
- 给
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; } }
- 修改
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; }
- 现在你的
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
相关产品推荐
相关产品推荐

