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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 04:47:03