Как работает async/await под капотом
Вопрос
Расскажите, как работает async/await в C# под капотом.
Попробуйте сами
Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.
Показать ответ и разбор
Коротко
Что должно прозвучать в ответе
Машина состояний: MoveNext, поля для локальных переменных, номер состояния
Паттерн awaiter: GetAwaiter, IsCompleted, OnCompleted, GetResult
Если задача уже завершена, продолжение идёт синхронно без переключений
Продолжение планируется через SynchronizationContext или TaskScheduler, иначе пул потоков
Для I/O потоки не заняты: завершение приходит через порты завершения (IOCP, epoll)
Исключения сохраняются в Task и пробрасываются в await
Что могут спросить следом
- Что изменится, если метод завершится синхронно?
- Где выполнится продолжение в консольном приложении?
- Почему ConfigureAwait(false) ускоряет библиотечный код?
Разбор
Компилятор превращает async-метод в машину состояний. Локальные переменные, нужные после await, становятся её полями, а код разбивается на куски по точкам ожидания. Метод MoveNext выполняет кусок до следующего await.
На await вызывается GetAwaiter(). Если IsCompleted уже true, выполнение продолжается синхронно, без переключений и аллокаций. Если нет, машина запоминает состояние, регистрирует себя через OnCompleted и возвращает управление вызывающему, а тот получает незавершённый Task. При первом реальном ожидании машина переезжает в кучу.
Когда операция завершается, продолжение планируется: если был захвачен SynchronizationContext (UI), то через него, иначе на пул потоков. Для ввода-вывода никакой поток не ждёт: ОС уведомляет через порты завершения, и только тогда берётся поток пула. Результат или исключение хранятся в задаче, и GetResult либо возвращает значение, либо пробрасывает исключение с исходным стеком.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.