.NET Разработчик
6.55K subscribers
442 photos
3 videos
14 files
2.12K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2011. #ЗаметкиНаПолях #AsyncTips
Конечный Автомат в C# для async/await. Окончание

Начало

Планировщик заданий
Продолжения (обратные вызовы) размещаются в планировщике заданий (TaskScheduler). Он берёт конечный автомат и планирует его выполнение после завершения ожидаемой задачи. Таким образом, TaskScheduler отвечает за продолжения. Здесь есть ещё несколько интересных моментов, таких как контекст синхронизации (SynchronizationContext) и пул потоков (ThreadPool), но это детали.

Итак: при использовании async/await компилятор разделит метод по границам await и создаст конечный автомат. Этот конечный автомат будет запланирован в TaskScheduler для продолжения после завершения ожидаемой задачи.

Где хранится конечный автомат?
В общем случае - в Task или Task<T>. Там хранится текущее состояние (включая продолжения), а также результат ожидаемой задачи. Но, кроме того, объект Task также хранит исключения. Исключения в с async/await-коде немного отличаются от исключений в синхронном коде.

Рассмотрим следующий код:
static async Task ThrowExceptionAsync()
{
await Task.Yield();
throw new Exception("Ex");
}

Компилятор преобразует этот код в следующий:
try
{
YieldAwaitable.YieldAwaiter awaiter;
// Другой код
awaiter.GetResult();
throw new Exception("Ex");
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
}

Важно отметить, что внутри блока catch нет throw. Т.е., если возникнет исключение в асинхронной части метода, оно не будет выброшено, а будет сохранено в объекте Task.

Возможно, теперь вы понимаете, почему async void — плохая идея: исключения будут потеряны. Они будут возникать, но вы не сможете их перехватить или обработать каким-либо образом. То же самое относится к async Task, если вы не ожидаете её. Поэтому:
static async Task ThrowExAsync()
{
throw new Exception("Ex");
await SomethingAsync();
}

// не выбросит исключения
_ = ThrowExAsync();

// не выбросит исключения
ThrowExAsync();

// выбросит исключение
await ThrowExAsync();

Исключение перехватывается и сохраняется в объекте Task, не выбрасываясь во внешний код. Но когда вы ожидаете объект Task, вызов await будет преобразован во что-то вроде GetAwaiter().GetResult(), и именно здесь исключение выбрасывается из объекта Task наружу.

Источник: https://steven-giesel.com/blogPost/720a48fd-0abe-4c32-83ac-26926d501895/the-state-machine-in-c-with-asyncawait
👍16👎1