Почему эти асинхронные методы C # не выполняются после Task.Delay ()? - PullRequest
0 голосов
/ 27 апреля 2019

Я пытаюсь понять 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 . . .
* 1018Теперь, что случилось с «конечными» утверждениями?Что происходит с асинхронным ожиданием в этих примерах?

Ответы [ 4 ]

4 голосов
/ 27 апреля 2019

Ваша программа начинается с вызова Main и завершается по завершении.

Поскольку Main просто создает экземпляр Program, а затем вызывает MakeBreakfast(), который возвращает Task вернуться к основному, как только он достигнет своего первого 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!
2 голосов
/ 27 апреля 2019

Общий рабочий процесс асинхронного метода заключается в том, что код, пока ожидание не будет выполнено синхронно (т. Е. Как есть), затем возвращается объект задачи с ожидаемой задачей, а все содержимое после ожидания помещается в продолжение. этой задачи, которая должна быть выполнена после ее завершения.

Теперь, если ожидается только BoilWater, стартовое сообщение выполняется синхронно, а все остальные вызовы помещаются как продолжение. Поскольку MakeBreakfast не ожидается, программа выполнится до того, как BoilWater сможет завершить / подождать свои миллисекунды, и, следовательно, продолжения (то есть другие задачи) не будут выполнены.

Если BoilWater не ожидается, другие задачи MakeBreakfast не ставятся как продолжение задачи BoilWater. Это означает, что BoilWater снова запускается до Task.Delay и возвращает его как задачу. Однако, поскольку эта задача не ожидается, следующая MakeBreakfast запускается таким же образом. По сути, все MakeBreakfast задачи запускаются последовательно, и MakeBreakfast может вернуться только тогда, когда SpreadWater запущен и возвращает свою задачу. Опять же, задачи по-прежнему выполняются в фоновом режиме, ожидая своих миллисекунд, но программа завершает работу до этого периода времени, и, таким образом, продолжения заключительного сообщения не имеют шансов на запуск.

1 голос
/ 27 апреля 2019

Вы в основном используете паттерн «забей и забудь», и этого почти всегда следует избегать.

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 () может привести к неудачному началу дня.

0 голосов
/ 27 апреля 2019

Что случилось с оператором "Boilwater end" и всеми другими вызовами методов?Если я добавлю async / await только в метод BoilWater, я получу тот же вывод.

Как было сказано zerkms, ваша программа завершает работу до завершения задачи.

Если я удаляю await из всех вызовов методов -

Я считаю, что если await нигде не вызывается в асинхронной задаче, то она просто обрабатывает ее синхронно;это объясняет, почему в выходных данных отображаются все «стартовые» сообщения.

Что касается того, что случилось с «конечными» утверждениями, я считаю, что если вы не дождетесь Task.Delay, то на самом деле он не будет ждать (задерживаться), и ваш код продолжится.На самом деле я только что нашел этот , который объясняет это лучше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...