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

时间:2019-04-27 03:04:54

标签: c# async-await

我试图理解C#异步/等待,并观察到这种令人困惑的行为,即异步方法无法通过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方法中,则会得到相同的输出。

如果我从所有方法调用中删除了等待-

    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 . . .
     

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

4 个答案:

答案 0 :(得分:4)

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

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

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

Main

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

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

代码确实确实命中了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(); } ,然后返回到await

要使代码正确完成,我们需要Main所有内容。您可以通过两种方式执行此操作:

(1)

await

现在运行此命令时,将得到以下输出:

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()
{
    await BoilWater();
    await StartToaster();
    await PutTeainWater();
    await PutBreadinToaster();
    await SpreadButter();
}

现在此版本同时启动所有早餐任务,但等待它们全部完成后再返回。

您将获得以下输出:

BoilWater start
StartToaster start
PutTeainWater start
PutBreadinToaster start
SpreadButter start
StartToaster end
SpreadButter end
BoilWater end
PutTeainWater end
PutBreadinToaster end
Done!

另一种方法可以使代码更符合逻辑地执行-先煮开水,然后煮茶;然后开始烤面包机,煮吐司面包,传播吐司面包-可能是这样的:

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
StartToaster end
PutBreadinToaster start
BoilWater end
PutTeainWater start
PutTeainWater end
PutBreadinToaster end
SpreadButter start
SpreadButter end
Done!

答案 1 :(得分:2)

异步方法的一般工作流程是,直到await同步运行(即按原样)的代码,然后返回一个任务对象以及要等待的任务,并将await之后的所有内容作为继续该任务完成时将要执行的任务。

现在,如果仅等待BoilWater,则开始消息将同步执行,而所有其他调用将作为继续。由于未等待MakeBreakfast,因此程序将在BoilWater可以完成/等待毫秒之前执行,因此不会执行继续(即其他任务)。

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

答案 2 :(得分:1)

您通常使用即弃模式,这是几乎总是避免的模式。

async / await应该始终形成一条链,这是一种可能的解决方案:

static void Main(string[] args)
{
    Program p = new Program();
    p.MakeBreakfast().Wait();  // or use async Main() when available
}

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

尽管并行执行PutBreadinToaster()和SpreadButter()可能会使一天的开局不好。

答案 3 :(得分:-2)

  

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

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

  

如果我从所有方法调用中删除了等待-

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

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