Может ли поток возвращаться в пул потоков при использовании await
Что проверяют
Понимаете ли вы, что await не создаёт поток и не блокирует текущий, и где продолжится метод после ожидания. Следом почти всегда спрашивают про SynchronizationContext, ConfigureAwait(false) и почему .Result приводит к дедлоку.
Как отвечать
Да, может, и в серверном коде в этом весь смысл async. Механика такая:
- задача ещё не завершена: компилятор превращает метод в конечный автомат,
awaitрегистрирует продолжение и возвращает незавершённыйTaskвызывающему коду. Если метод шёл на потоке пула, поток освобождается и берёт другую работу; - задача уже завершена: например, данные взяты из кеша и возвращён готовый
ValueTask. Тогдаawaitпродолжает выполнение синхронно на том же потоке, переключения нет; - пока идёт ввод-вывод: потока, который «ждёт ответ», нет вообще. Операция висит на уровне ОС (IOCP в Windows, epoll в Linux), и по её завершении продолжение ставится в очередь.
Где продолжится метод, решает захваченный контекст. Если есть SynchronizationContext (UI-поток в WinForms и WPF, классический ASP.NET), продолжение вернётся в него. В ASP.NET Core и консольном приложении контекста нет, и продолжение выполнится на любом потоке пула, не обязательно на исходном. ConfigureAwait(false) отказывается от захвата контекста, его ставят в библиотечном коде, которому не нужен UI-поток.
Для CPU-задач await сам по себе ничего не освобождает: работу отдают в пул явно через Task.Run, но в ASP.NET Core делать это ради «асинхронности» не стоит, запрос и так идёт на потоке пула.
Где спотыкаются
Говорят, что await запускает операцию в новом потоке. Он только подписывается на завершение задачи.
Блокируют асинхронный код через .Result или .Wait(). В UI это дедлок: поток ждёт задачу, а задача ждёт этот же поток ради продолжения. На сервере это голодание пула: потоки заняты ожиданием, а новые пул добавляет постепенно.
Считают, что после await код продолжится на том же потоке. Это верно только при наличии контекста, поэтому ThreadLocal и [ThreadStatic] в асинхронном коде ломаются, для этого есть AsyncLocal<T>.