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

我试图理解 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"); }

}

它的输出将是 -


Boilwater Start

Press any key to continue...

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


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


    public async Task MakeBreakfast()

    {

         BoilWater();

         StartToaster();

         PutTeainWater();

         PutBreadinToaster();

         SpreadButter();

    }

Now, the output is - 

BoilWater start

StartToaster start

PutTeainWater start

PutBreadinToaster start

SpreadButter start

Press any key to continue . . .

现在,“结束”语句发生了什么?在这些示例中,async await 发生了什么?


白衣染霜花
浏览 306回答 3
3回答

慕容708150

您的程序从调用开始,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();}现在,如果我让它运行完成,我会看到这个输出:Starting MakeBreakfastCalling await BoilWater()BoilWater startDone!BoilWater endDone await BoilWater()StartToaster startPutTeainWater startStartToaster endPutBreadinToaster startSpreadButter startSpreadButter endPutTeainWater endPutBreadinToaster end代码确实点击了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();}现在当它运行时你会得到这个输出:BoilWater startBoilWater endStartToaster startStartToaster endPutTeainWater startPutTeainWater endPutBreadinToaster startPutBreadinToaster endSpreadButter startSpreadButter endDone!(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);}现在这个版本同时开始所有的早餐任务,但在返回之前等待它们全部完成。你得到这个输出:BoilWater startStartToaster startPutTeainWater startPutBreadinToaster startSpreadButter startStartToaster endSpreadButter endBoilWater endPutTeainWater endPutBreadinToaster endDone!一种更符合逻辑的代码执行方式——先烧水,再泡茶;然后启动烤面包机,煮吐司,摊开吐司——可能是这样的: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());}这给出了:BoilWater startStartToaster startStartToaster endPutBreadinToaster startBoilWater endPutTeainWater startPutTeainWater endPutBreadinToaster endSpreadButter startSpreadButter endDone!

跃然一笑

异步方法的一般工作流程是同步运行 await 之前的代码(即按原样运行),然后返回一个任务对象,其中包含要等待的任务,而 await 之后的所有内容都作为该任务的延续任务完成时执行。现在,如果只BoilWater等待启动消息同步执行,所有其他调用将作为延续。由于MakeBreakfast没有等待,程序将在BoilWater完成/等待它们的毫秒之前执行,因此不会执行延续(即其他任务)。如果BoilWater没有等待,则其他MakeBreakfast任务不会作为BoilWater任务的延续。这意味着BoilWater再次运行直到 Task.Delay 并将其作为任务返回。但是,由于没有等待此任务,因此下一个任务MakeBreakfast将以相同的方式启动。所以本质上,所有MakeBreakfast任务都是按顺序启动的,并且MakeBreakfast只能在SpreadWater启动时返回并返回它的任务。同样,任务仍在后台运行,等待它们的毫秒数,但程序在此时间范围之前退出,因此关闭消息延续没有机会运行。

青春有我

“Boilwater end”语句和所有其他方法调用发生了什么?如果我只将 async/await 放在 BoilWater 方法中,我会得到相同的输出。正如 zerkms 所说,您的程序在任务完成之前退出。如果我从所有方法调用中删除 await -我相信如果await没有在异步任务中的任何地方调用,那么它只是同步处理它;这可以解释为什么输出显示所有“开始”消息。至于“结束”语句发生了什么,我相信如果你不等待Task.Delay,那么它实际上不会等待(延迟)并且你的代码将继续。实际上我刚刚发现这个可以更好地解释它。
打开App,查看更多内容
随时随地看视频慕课网APP