C#中无法按正确顺序并发调用异步方法的问题排查
C#异步并行任务实现错误及修正方案
原始代码与需求
我正在学习C#异步方法,编写了一个按顺序调用方法并计算总耗时的小程序,代码如下:
public static async Task<string> MakeCoffeeAsync() { Thread.Sleep(3000); return("Coffee is Ready"); } public static async Task<string> ToastBreadAsync() { Thread.Sleep(2000); return ("Bread is Toasted"); } public static async Task<string> ApplyJamToBreadAsync() { Thread.Sleep(1000); return ("Jam added to Bread"); } public static async void MakeBreakfast() { Console.WriteLine("Making Breakfast async way"); Stopwatch sw = new Stopwatch(); sw.Start(); Console.WriteLine(await MakeCoffeeAsync()); Console.WriteLine(await ToastBreadAsync()); Console.WriteLine(await ApplyJamToBreadAsync()); sw.Stop(); Console.WriteLine("Normal Way - Total Time Taken " + sw.ElapsedMilliseconds / 1000 + " Seconds"); }
该代码正常运行,总耗时6秒符合预期。
现在希望实现两个任务并行执行:一个任务执行制作咖啡(耗时3秒),另一个任务先烤面包(耗时2秒),完成后再涂果酱(耗时1秒),烤面包和涂果酱的顺序不能颠倒。预期总耗时应为3秒,因为制作咖啡与(烤面包+涂果酱)并行。
尝试了以下代码,但运行后仅输出"Making Breakfast Concurrent way",无其他内容:
public static async void MakeBreakfastConcurrent() { Console.WriteLine("Making Breakfast Concurrent way"); Stopwatch sw = new Stopwatch(); sw.Start(); await Task.Run(() => Console.WriteLine(MakeCoffeeAsync())); await Task.Run(() => { Console.WriteLine(ToastBreadAsync()); Console.WriteLine(ApplyJamToBreadAsync()); }); sw.Stop(); Console.WriteLine("Normal Way - Total Time Taken " + sw.ElapsedMilliseconds / 1000 + " Seconds"); }
错误分析
- 错误使用
Thread.Sleep:异步方法中使用Thread.Sleep会阻塞当前线程,完全违背异步非阻塞的设计初衷,应该替换为非阻塞的await Task.Delay()。 - 未正确等待异步任务:
MakeCoffeeAsync()、ToastBreadAsync()、ApplyJamToBreadAsync()调用时未加await,导致直接输出Task对象而非方法返回值,且任务未被等待完成;同时Task.Run的lambda内部未等待异步方法完成,任务可能在未执行完毕就被终止。 - 任务未真正并行:两个
Task.Run被顺序await,实际是串行执行,而非并行运行。
正确实现方案
第一步:修正异步方法的延迟方式
将所有Thread.Sleep替换为await Task.Delay,保证异步方法非阻塞:
public static async Task<string> MakeCoffeeAsync() { await Task.Delay(3000); return "Coffee is Ready"; } public static async Task<string> ToastBreadAsync() { await Task.Delay(2000); return "Bread is Toasted"; } public static async Task<string> ApplyJamToBreadAsync() { await Task.Delay(1000); return "Jam added to Bread"; }
第二步:实现并行任务逻辑
先定义一个组合任务(烤面包+涂果酱),再同时启动咖啡任务和组合任务,最后等待两者都完成,实现并行执行:
public static async void MakeBreakfastConcurrent() { Console.WriteLine("Making Breakfast Concurrent way"); Stopwatch sw = new Stopwatch(); sw.Start(); // 定义组合任务:先烤面包,再涂果酱 var breadTask = Task.Run(async () => { Console.WriteLine(await ToastBreadAsync()); Console.WriteLine(await ApplyJamToBreadAsync()); }); // 启动咖啡任务 var coffeeTask = Task.Run(async () => { Console.WriteLine(await MakeCoffeeAsync()); }); // 等待两个并行任务都完成 await Task.WhenAll(breadTask, coffeeTask); sw.Stop(); Console.WriteLine("Concurrent Way - Total Time Taken " + sw.ElapsedMilliseconds / 1000 + " Seconds"); }
执行效果
运行后会输出类似内容,总耗时约3秒,符合预期:
Making Breakfast Concurrent way Bread is Toasted Jam added to Bread Coffee is Ready Concurrent Way - Total Time Taken 3 Seconds
补充:类似JS的.then()链式调用实现
如果想要实现类似JS中.then()的顺序链式调用,可以先封装组合逻辑:
public static async Task PrepareBreadAsync() { var toastResult = await ToastBreadAsync(); Console.WriteLine(toastResult); var jamResult = await ApplyJamToBreadAsync(); Console.WriteLine(jamResult); }
然后在并行逻辑中调用,也可以用ContinueWith模拟.then():
var breadTask = PrepareBreadAsync(); var coffeeTask = MakeCoffeeAsync().ContinueWith(t => Console.WriteLine(t.Result)); await Task.WhenAll(breadTask, coffeeTask);
内容的提问来源于stack exchange,提问作者Bluemarble
相关产品推荐
相关产品推荐

