Почему async не возвращает управление вызывающей стороне после нажатия await?

Я пытаюсь понять, как работает async/await в С#, особенно в отношении поведения потока. Я читал сообщение в блоге Стивена Клири об асинхронности и ожидании (https://blog.stephencleary.com/2012/02/async-and-await.html), и одна часть документации меня смущает:

Начало асинхронного метода выполняется так же, как и любой другой метод. То есть он выполняется синхронно до тех пор, пока не достигнет «ожидания» (или выдает исключение).

У меня есть следующий код:

class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine($"Thread Id entering Main Method: {Thread.CurrentThread.ManagedThreadId}");
        await Something_One();
    }
    static async Task Something_One()
    {
        Console.WriteLine($"Thread Id entering Something_One: {Thread.CurrentThread.ManagedThreadId}");
        await Something_Two();
    } 
    
    static async Task Something_Two()
    {
        Console.WriteLine($"Thread Id Entring Something_Two: {Thread.CurrentThread.ManagedThreadId}");
        await Task.Delay(1000);
    }
}

Результат, который я вижу:

Thread Id entering Main Method: 1
Thread Id entering Something_One: 1
Thread Id Entring Something_Two: 1

Согласно документации, я ожидал, что синхронное выполнение остановится при первом встреченном ключевом слове await (и, возможно, продолжится в другом потоке), что есть await Something_Two() в методе Something_One. Однако выходные данные показывают, что Something_Two также выполняется синхронно, пока не достигнет строки await Task.Delay(1000).

Почему Something_Two выполняется синхронно, хотя оно вызывается из выражения ожидания в Something_One?

Я пытаюсь понять причину поведения потока, которое я наблюдаю, особенно в свете утверждения документации о том, что асинхронные методы выполняются синхронно, пока не достигнут ожидания. Может кто-нибудь объяснить, что происходит под капотом?

Отвечает ли это на ваш вопрос? Понимание async/await в C#

Progman 28.04.2024 11:23

Не используйте консольное приложение для обучения асинхронному ожиданию; они, очевидно, больше ничего не делают, пока ожидается операция. Используйте, например, приложение WinForms с таймером, при котором метка обновляется с текущим временем. В обработчике нажатия кнопки выполните Thread.Sleep(10000), а в другом обработчике кнопки дождитесь Task.Delay(10000). Сон потока заблокирует пользовательский интерфейс на 10 секунд, потому что тот же самый поток, который обрабатывает все записанные вами события, блокируется сном, поэтому он не может отобразить их или обслужить тик таймера. В версии с задержкой задачи хлеб сохраняет свое состояние и исчезает.

flackoverstow 28.04.2024 11:33

...создайте резервную копию стека вызовов в то место, где он обычно находится, когда не выполняется ваш код, т.е. в то место, где он обрабатывает оконные сообщения и другие вещи (не заглушая пользовательский интерфейс)

flackoverstow 28.04.2024 11:36
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
72
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

await визуально размещается в левой части строки, поэтому вы логически предположили, что он выполняется первым. На самом деле он выполняется после вызова асинхронного метода, следующего за await. Возможно, станет яснее, если мы перепишем ваш пример следующим образом:

class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine($"Thread Id entering Main Method: {Thread.CurrentThread.ManagedThreadId}");
        Task task = Something_One();
        await task;
    }
    static async Task Something_One()
    {
        Console.WriteLine($"Thread Id entering Something_One: {Thread.CurrentThread.ManagedThreadId}");
        Task task = Something_Two();
        await task;
    } 
    
    static async Task Something_Two()
    {
        Console.WriteLine($"Thread Id Entring Something_Two: {Thread.CurrentThread.ManagedThreadId}");
        Task task = Task.Delay(1000);
        await task;
    }
}

Вышеописанное полностью соответствует вашему примеру. Теперь становится очевидно, что весь код будет выполняться синхронно, пока не будет дождаться Task.Delay(1000). Тогда все эти методы вернут вызывающему объекту неполный Task. В конечном итоге основной поток консольного приложения будет заблокирован до завершения задачи Task.Delay(1000), потому что именно так ведут себя консольные приложения с точкой входа async Main (подробности см. в документации ).

теперь это имеет смысл, большое спасибо

yyhnfd 28.04.2024 16:53

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

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