Что делает ConfigureAwait в C# async/await
Что проверяют
Знаете ли вы, что делает await с контекстом синхронизации, и отличаете ли UI-приложение от ASP.NET Core. Вопрос почти всегда продолжается классическим дедлоком с .Result и уточнением, нужен ли ConfigureAwait(false) в коде веб-API.
Как отвечать
Начните с самого await. Перед ожиданием он запоминает SynchronizationContext.Current (если его нет, то нестандартный TaskScheduler.Current) и после завершения задачи отправляет продолжение туда. В WPF, WinForms и MAUI это UI-поток, поэтому после await можно спокойно трогать контролы.
ConfigureAwait(false): говорит не захватывать контекст. Продолжение выполнится там, где завершилась задача, обычно на потоке пула;- зачем в библиотеках: библиотечному коду UI-поток не нужен, а возврат в него стоит лишнего маршалинга и создаёт риск дедлока у потребителя, который блокируется на
.Resultили.Wait(); - ASP.NET Core:
SynchronizationContextтам нет вообще, поэтому в коде приложения вызов ничего не меняет. В классическом ASP.NET на .NET Framework контекст был, и там это имело значение; - .NET 8: появилась перегрузка с
ConfigureAwaitOptions, напримерSuppressThrowing(дождаться задачи без выброса исключения) иForceYielding(обязательно уйти в асинхронное продолжение); await foreachиawait using: для них тоже есть.ConfigureAwait(false), о чём часто забывают.
В UI-обработчике после ConfigureAwait(false) обращение к контролу закончится InvalidOperationException из-за доступа из чужого потока. Для контроля в библиотеках включают анализатор CA2007, он подсветит каждый await без явного выбора.
Где спотыкаются
Считают, что ConfigureAwait(false) запускает код в фоне или создаёт поток. Он ничего не запускает, а лишь решает, куда вернуть продолжение.
Ставят его только на первый await и считают метод защищённым. Если задача уже завершена, продолжение выполнится синхронно в прежнем контексте, поэтому в библиотеке его пишут на каждом await.
Лечат дедлок расстановкой ConfigureAwait(false). Настоящее исправление: не блокироваться на асинхронном коде и пройти async до конца цепочки вызовов.