为什么这些 C# 异步方法不通过 Task.Delay() 执行?

Ach*_*les 2 c# async-await

我试图理解 C# async/await 并观察到这种令人困惑的行为,其中异步方法不执行过去的Task.Delay调用。

考虑以下 -

class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();
        p.MakeBreakfast();
    }

    public async Task MakeBreakfast()
    {
        await BoilWater();
        StartToaster();
        PutTeainWater();
        PutBreadinToaster();
        SpreadButter();
    }

    public async Task BoilWater() { Console.WriteLine("BoilWater start"); await Task.Delay(30); Console.WriteLine("BoilWater end"); }
    public async Task StartToaster() { Console.WriteLine("StartToaster start"); await Task.Delay(1); Console.WriteLine("StartToaster end"); }
    public async Task PutBreadinToaster() { Console.WriteLine("PutBreadinToaster start"); await Task.Delay(2000); Console.WriteLine("PutBreadinToaster end"); }
    public async Task PutTeainWater() { Console.WriteLine("PutTeainWater start"); await Task.Delay(30); Console.WriteLine("PutTeainWater end"); }
    public async Task SpreadButter() { Console.WriteLine("SpreadButter start"); await Task.Delay(10); Console.WriteLine("SpreadButter end"); }
}
Run Code Online (Sandbox Code Playgroud)

它的输出将是 -

Boilwater Start
Press any key to continue...
Run Code Online (Sandbox Code Playgroud)

“Boilwater end”语句和所有其他方法调用发生了什么变化?如果我只将 async/await 放在 BoilWater 方法中,我会得到相同的输出。

如果我从所有方法调用中删除 await -

    public async Task MakeBreakfast()
    {
         BoilWater();
         StartToaster();
         PutTeainWater();
         PutBreadinToaster();
         SpreadButter();
    }
Run Code Online (Sandbox Code Playgroud)
Now, the output is - 
BoilWater start
StartToaster start
PutTeainWater start
PutBreadinToaster start
SpreadButter start
Press any key to continue . . .
Run Code Online (Sandbox Code Playgroud)

现在,“结束”语句发生了什么?在这些示例中,异步等待发生了什么?

Eni*_*ity 5

您的程序从调用开始,Main并在完成后退出。

由于Main只是创建了一个 的实例,Program然后调用MakeBreakfast()Task它会在遇到第一个 时立即返回到 main await。因此Main几乎立即存在。

让我们稍微更改一下代码,看看是否是这种情况:

static void Main(string[] args)
{
    Program p = new Program();
    p.MakeBreakfast();
    Console.WriteLine("Done!");
    Console.ReadLine();
}

public async Task MakeBreakfast()
{
    Console.WriteLine("Starting MakeBreakfast");
    Thread.Sleep(1000);
    Console.WriteLine("Calling await BoilWater()");
    await BoilWater();
    Console.WriteLine("Done await BoilWater()");
    StartToaster();
    PutTeainWater();
    PutBreadinToaster();
    SpreadButter();
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我让它运行完成,我会看到以下输出:

开始制作早餐
调用 await BoilWater()
开水开始
完毕!
沸水端
完成等待 BoilWater()
开始烤面包机开始
PutTeainWater 开始
开始烤面包机结束
PutBreadinToaster 启动
涂抹黄油开始
涂抹黄油结束
放茶水端
PutBreadinToaster 结束

代码确实命中了await然后返回到Main.

为了使代码正确完成,我们需要await一切。您有两种方法可以做到这一点:

(1)

static async Task Main(string[] args)
{
    Program p = new Program();
    await p.MakeBreakfast();
    Console.WriteLine("Done!");
    Console.ReadLine();
}

public async Task MakeBreakfast()
{
    await BoilWater();
    await StartToaster();
    await PutTeainWater();
    await PutBreadinToaster();
    await SpreadButter();
}
Run Code Online (Sandbox Code Playgroud)

现在当它运行时,你会得到这个输出:

开水开始
沸水端
开始烤面包机开始
开始烤面包机结束
PutTeainWater 开始
放茶水端
PutBreadinToaster 启动
PutBreadinToaster 结束
涂抹黄油开始
涂抹黄油结束
完毕!

(2)

static async Task Main(string[] args)
{
    Program p = new Program();
    await p.MakeBreakfast();
    Console.WriteLine("Done!");
    Console.ReadLine();
}

public async Task MakeBreakfast()
{
    var tasks = new[]
    {
        BoilWater(),
        StartToaster(),
        PutTeainWater(),
        PutBreadinToaster(),
        SpreadButter(),
    };
    await Task.WhenAll(tasks);
}
Run Code Online (Sandbox Code Playgroud)

现在这个版本同时开始所有的早餐任务,但在返回之前等待它们全部完成。

你得到这个输出:

开水开始
开始烤面包机开始
PutTeainWater 开始
PutBreadinToaster 启动
涂抹黄油开始
开始烤面包机结束
涂抹黄油结束
沸水端
放茶水端
PutBreadinToaster 结束
完毕!

一种更合乎逻辑地执行代码的替代方案 - 先煮沸水,然后泡茶;然后启动烤面包机,烤面包,铺烤面包 - 可能是这样的:

public async Task MakeBreakfast()
{
    async Task MakeTea()
    {
        await BoilWater();
        await PutTeainWater();      
    }

    async Task MakeToast()
    {
        await StartToaster();
        await PutBreadinToaster();
        await SpreadButter();           
    }       
    await Task.WhenAll(MakeTea(), MakeToast());
}
Run Code Online (Sandbox Code Playgroud)

这给出了:

开水开始
开始烤面包机开始
开始烤面包机结束
PutBreadinToaster 启动
沸水端
PutTeainWater 开始
放茶水端
PutBreadinToaster 结束
涂抹黄油开始
涂抹黄油结束
完毕!

  • 考虑使用“async Task Main”而不是“async void Main”。那么你甚至不需要`Console.ReadLine`。 (2认同)