Я пытаюсь понять 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...What happened to "Boilwater end" statement and all other method calls? If I put async/await only in the BoilWater method, I get the same output.
Если я удалю ожидание из всех вызовов методов -
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 . . .Now, what happened to "end" statements? What's going on with async await in these examples?
@Achilles, можете ли вы опубликовать точный код, который вы используете. Там что-то не так. Чтобы получить сообщение «Нажмите любую клавишу для продолжения…» напечатано, вам понадобится Console.Readline(). Если вы введете это, вы получите ожидаемый результат. Если вы этого не сделаете, программа завершится до того, как некоторые из Console.Writelines() смогут выполниться.





What happened to "Boilwater end" statement and all other method calls? If I put async/await only in the BoilWater method, I get the same output.
Как было сказано zerkms, ваша программа завершает работу до того, как ваша задача будет завершена.
If I remove await from all method calls -
Я считаю, что если await не вызывается нигде в асинхронной задаче, то она просто обрабатывает ее синхронно; это объясняет, почему на выходе отображаются все «стартовые» сообщения.
Что касается того, что случилось с операторами «конца», я считаю, что если вы не ждете Task.Delay, то он фактически не будет ждать (задерживать), и ваш код будет продолжаться. На самом деле я только что нашел это, который объясняет это лучше.
Общий рабочий процесс асинхронного метода заключается в том, что код до ожидания запускается синхронно (т.е. как есть), затем возвращается объект задачи с ожидаемой задачей, и все, что после ожидания помещается как продолжение этой задачи который должен быть выполнен, когда задача завершится.
Теперь, если ожидается только BoilWater, стартовое сообщение выполняется синхронно, а все остальные вызовы ставятся как продолжение. Поскольку MakeBreakfast не ожидается, программа выполнится до того, как BoilWater сможет завершить/подождать свои миллисекунды, и, следовательно, продолжения (то есть другие задачи) не выполняются.
Если BoilWater не ожидается, другие MakeBreakfast задачи не ставятся как продолжение BoilWater задачи. Это означает, что BoilWater снова запускается до Task.Delay и возвращает это как задачу. Однако, поскольку эта задача не ожидается, следующая MakeBreakfast запускается таким же образом. По сути, все MakeBreakfast задачи запускаются последовательно, и MakeBreakfast может вернуться только тогда, когда SpreadWater запускается и возвращает свою задачу. Опять же, задачи по-прежнему выполняются в фоновом режиме, ожидая своих миллисекунд, но программа завершает работу до этого периода времени, и, таким образом, продолжения сообщения о закрытии не могут быть запущены.
«все, что после ожидания ставится как продолжение этой задачи, которое должно быть выполнено после завершения задачи» - я не верю, что есть продолжения задачи. Все дело в государственных машинах. Взгляните на System.Runtime.CompilerServices.AsyncTaskMethodBuilder.
@Enigmativity Правда, материал after-await не является реальным продолжением, таким как ContinueWith, поскольку последний гораздо более упрощен (например, не рассматривает возобновление в том же контексте синхронизации) и, следовательно, не нуждается в сложном конечном автомате. Но в целом (и особенно когда нет контекста синхронизации, как на консоли), await task; DoStuff(); return; следует примерно тому же рабочему процессу, что и return task.ContinueWith(t => DoStuff(), TaskContinuationOptions.OnlyOnRanToCompletion);. Я говорил с точки зрения рабочего процесса, а не совсем с точки зрения реализации.
Ваша программа начинается с вызова 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!
Я уверен, что вы должны await Вскипятить воду до вы Положите RainWater.
@HenkHolterman - Да, это правда. Логично, что здесь должно произойти именно это, потому что мы понимаем, что означает каждый шаг. Я просто воспринял это как набор абстрактных задач.
@HenkHolterman - я добавил реальную логическую версию кода.
Попробуйте использовать async Task Main вместо async void Main. Тогда вам даже не нужен Console.ReadLine.
Ваша программа закрывается до того, как
MakeBreakfastуспевает завершиться. Вы должны либоawait, либо вернуть значениеasync, в противном случае полнота не гарантируется.