Я пытаюсь понять, как работает 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?
Я пытаюсь понять причину поведения потока, которое я наблюдаю, особенно в свете утверждения документации о том, что асинхронные методы выполняются синхронно, пока не достигнут ожидания. Может кто-нибудь объяснить, что происходит под капотом?
Не используйте консольное приложение для обучения асинхронному ожиданию; они, очевидно, больше ничего не делают, пока ожидается операция. Используйте, например, приложение WinForms с таймером, при котором метка обновляется с текущим временем. В обработчике нажатия кнопки выполните Thread.Sleep(10000), а в другом обработчике кнопки дождитесь Task.Delay(10000). Сон потока заблокирует пользовательский интерфейс на 10 секунд, потому что тот же самый поток, который обрабатывает все записанные вами события, блокируется сном, поэтому он не может отобразить их или обслужить тик таймера. В версии с задержкой задачи хлеб сохраняет свое состояние и исчезает.
...создайте резервную копию стека вызовов в то место, где он обычно находится, когда не выполняется ваш код, т.е. в то место, где он обрабатывает оконные сообщения и другие вещи (не заглушая пользовательский интерфейс)
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
(подробности см. в документации ).
теперь это имеет смысл, большое спасибо
Отвечает ли это на ваш вопрос? Понимание async/await в C#