Почему эти асинхронные методы С# не выполняются после 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...

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?

Ваша программа закрывается до того, как MakeBreakfast успевает завершиться. Вы должны либо await, либо вернуть значение async, в противном случае полнота не гарантируется.

zerkms 27.04.2019 05:08

@Achilles, можете ли вы опубликовать точный код, который вы используете. Там что-то не так. Чтобы получить сообщение «Нажмите любую клавишу для продолжения…» напечатано, вам понадобится Console.Readline(). Если вы введете это, вы получите ожидаемый результат. Если вы этого не сделаете, программа завершится до того, как некоторые из Console.Writelines() смогут выполниться.

Phillip Ngan 27.04.2019 07:40
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
157
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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 27.04.2019 07:31

@Enigmativity Правда, материал after-await не является реальным продолжением, таким как ContinueWith, поскольку последний гораздо более упрощен (например, не рассматривает возобновление в том же контексте синхронизации) и, следовательно, не нуждается в сложном конечном автомате. Но в целом (и особенно когда нет контекста синхронизации, как на консоли), await task; DoStuff(); return; следует примерно тому же рабочему процессу, что и return task.ContinueWith(t => DoStuff(), TaskContinuationOptions.OnlyOnRanToCompletion);. Я говорил с точки зрения рабочего процесса, а не совсем с точки зрения реализации.

ckuri 27.04.2019 07:45
Ответ принят как подходящий

Ваша программа начинается с вызова 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.

Henk Holterman 27.04.2019 07:33

@HenkHolterman - Да, это правда. Логично, что здесь должно произойти именно это, потому что мы понимаем, что означает каждый шаг. Я просто воспринял это как набор абстрактных задач.

Enigmativity 27.04.2019 07:36

@HenkHolterman - я добавил реальную логическую версию кода.

Enigmativity 27.04.2019 07:49

Попробуйте использовать async Task Main вместо async void Main. Тогда вам даже не нужен Console.ReadLine.

Stephen Cleary 27.04.2019 13:28

Другие вопросы по теме

Похожие вопросы