为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
白衣染霜花 2022-12-31 13:28:50
我试图理解 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 StartPress any key to continue...“Boilwater end”语句和所有其他方法调用发生了什么?如果我只将 async/await 放在 BoilWater 方法中,我会得到相同的输出。如果我从所有方法调用中删除 await -    public async Task MakeBreakfast()    {         BoilWater();         StartToaster();         PutTeainWater();         PutBreadinToaster();         SpreadButter();    }Now, the output is - BoilWater startStartToaster startPutTeainWater startPutBreadinToaster startSpreadButter startPress any key to continue . . .现在,“结束”语句发生了什么?在这些示例中,async await 发生了什么?
查看完整描述

3 回答

?
慕容708150

TA贡献1831条经验 获得超4个赞

您的程序从调用开始,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 MakeBreakfast

Calling await BoilWater()

BoilWater start

Done!

BoilWater end

Done await BoilWater()

StartToaster start

PutTeainWater start

StartToaster end

PutBreadinToaster start

SpreadButter start

SpreadButter end

PutTeainWater end

PutBreadinToaster 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 start

BoilWater end

StartToaster start

StartToaster end

PutTeainWater start

PutTeainWater end

PutBreadinToaster start

PutBreadinToaster end

SpreadButter start

SpreadButter end

Done!

(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 start

StartToaster start

PutTeainWater start

PutBreadinToaster start

SpreadButter start

StartToaster end

SpreadButter end

BoilWater end

PutTeainWater end

PutBreadinToaster end

Done!

一种更符合逻辑的代码执行方式——先烧水,再泡茶;然后启动烤面包机,煮吐司,摊开吐司——可能是这样的:


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 start

StartToaster start

StartToaster end

PutBreadinToaster start

BoilWater end

PutTeainWater start

PutTeainWater end

PutBreadinToaster end

SpreadButter start

SpreadButter end

Done!


查看完整回答
反对 回复 2022-12-31
?
跃然一笑

TA贡献1826条经验 获得超6个赞

异步方法的一般工作流程是同步运行 await 之前的代码(即按原样运行),然后返回一个任务对象,其中包含要等待的任务,而 await 之后的所有内容都作为该任务的延续任务完成时执行。

现在,如果只BoilWater等待启动消息同步执行,所有其他调用将作为延续。由于MakeBreakfast没有等待,程序将在BoilWater完成/等待它们的毫秒之前执行,因此不会执行延续(即其他任务)。

如果BoilWater没有等待,则其他MakeBreakfast任务不会作为BoilWater任务的延续。这意味着BoilWater再次运行直到 Task.Delay 并将其作为任务返回。但是,由于没有等待此任务,因此下一个任务MakeBreakfast将以相同的方式启动。所以本质上,所有MakeBreakfast任务都是按顺序启动的,并且MakeBreakfast只能在SpreadWater启动时返回并返回它的任务。同样,任务仍在后台运行,等待它们的毫秒数,但程序在此时间范围之前退出,因此关闭消息延续没有机会运行。


查看完整回答
反对 回复 2022-12-31
?
青春有我

TA贡献1784条经验 获得超8个赞

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

正如 zerkms 所说,您的程序在任务完成之前退出。

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

我相信如果await没有在异步任务中的任何地方调用,那么它只是同步处理它;这可以解释为什么输出显示所有“开始”消息。

至于“结束”语句发生了什么,我相信如果你不等待Task.Delay,那么它实际上不会等待(延迟)并且你的代码将继续。实际上我刚刚发现这个可以更好地解释它。


查看完整回答
反对 回复 2022-12-31
  • 3 回答
  • 0 关注
  • 205 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号