На рисунке 3 показан простой пример, в котором один метод блокирует результат асинхронного метода.Этот код будет отлично работать в консольном приложении, но будет вызывать взаимную блокировку при вызове из контекста GUI или ASP.NETТакое поведение может сбивать с толку, особенно учитывая, что выполнение отладчика подразумевает, что это ожидание никогда не завершается.Фактическая причина взаимоблокировки заключается в том, что при вызове Task.Wait происходит дальнейшее повышение уровня стека вызовов.
Рис. 3. Типичная проблема взаимоблокировки при блокировке асинхронного кода
public static class DeadlockDemo
{
private static async Task DelayAsync()
{
await Task.Delay(1000);
}
// This method causes a deadlock when called in a GUI or ASP.NET context.
public static void Test()
{
// Start the delay.
var delayTask = DelayAsync();
// Wait for the delay to complete.
delayTask.Wait();
}
}
Основная причина этоготупик возникает из-за того, как ожидают обработки контекста.По умолчанию, когда ожидается незавершенное задание, текущий «контекст» фиксируется и используется для возобновления метода после завершения задания.Этот «контекст» является текущим SynchronizationContext, если он не равен нулю, в этом случае это текущий TaskScheduler.Приложения GUI и ASP.NET имеют SynchronizationContext, который позволяет одновременно запускать только один кусок кода.Когда ожидание завершается, оно пытается выполнить оставшуюся часть асинхронного метода в захваченном контексте.Но этот контекст уже содержит поток, который (синхронно) ожидает завершения асинхронного метода.Каждый из них ждет другого, вызывая тупик.
Обратите внимание, что консольные приложения не вызывают этот тупик.У них есть SynchronizationContext пула потоков вместо SynchronizationContext по одному блоку за раз, поэтому, когда await завершает свою работу, он планирует оставшуюся часть асинхронного метода в потоке пула потоков.Метод может завершиться, что завершает возвращаемое задание, и нет тупика.Эта разница в поведении может сбивать с толку, когда программисты пишут тестовую консольную программу, наблюдают, как частично асинхронный код работает должным образом, а затем перемещают тот же код в приложение с графическим интерфейсом или ASP.NET, где оно блокируется.
Лучшее решение этой проблемы - позволить асинхронному коду расти естественным образом через кодовую базу.Если вы последуете этому решению, вы увидите расширение асинхронного кода до точки входа, обычно это обработчик событий или действие контроллера.Консольные приложения не могут полностью следовать этому решению, потому что метод Main не может быть асинхронным.Если метод Main был асинхронным, он мог вернуться до завершения, что привело к завершению программы.На рисунке 4 показано это исключение из руководства. Метод Main для консольного приложения является одной из немногих ситуаций, когда код может блокироваться в асинхронном методе.
Рисунок 4 Метод Main может вызывать Task.Wait или Task.Результат
class Program
{
static void Main()
{
MainAsync().Wait();
}
static async Task MainAsync()
{
try
{
// Asynchronous implementation.
await Task.Delay(1000);
}
catch (Exception ex)
{
// Handle exceptions.
}
}
}
УЗНАТЬ БОЛЬШЕ ЗДЕСЬ