Дают ли async и await преимущества по сравнению с использованием потоков напрямую
Что проверяют
Понимаете ли вы, что async/await не про потоки, а про ожидание без блокировки. Интервьюер хочет услышать разницу между I/O-bound и CPU-bound работой. Следом обычно спрашивают, откуда берётся дедлок при .Result и создаёт ли await новый поток.
Как отвечать
Главная мысль: пока идёт ввод-вывод (запрос к БД, HTTP, чтение файла), держать поток незачем. Операционная система сообщит о завершении через порт завершения ввода-вывода в Windows или epoll в Linux, а продолжение метода выполнится на свободном потоке пула. Отсюда преимущества перед ручными потоками:
- масштабируемость: поток дорог, у него свой стек порядка мегабайта, переключения контекста стоят времени. В ASP.NET Core тысяча одновременных запросов с
awaitобслуживается небольшим пулом, а при блокирующем коде пул голодает и растут задержки; - отзывчивость UI: в WPF или WinForms
awaitосвобождает UI-поток, а продолжение возвращается в него черезSynchronizationContext, без ручногоDispatcher.Invoke; - композиция:
Task.WhenAll,Task.WhenAny, отмена черезCancellationToken, исключения доходят до вызывающего кода, а не теряются в чужом потоке; - читаемость: код выглядит последовательным, конечный автомат строит компилятор.
Назовите и границу. Для CPU-bound работы async ничего не ускоряет: вычисление всё равно займёт поток, Task.Run лишь переносит его в пул. Для параллельных расчётов есть Parallel.ForEach и PLINQ, а отдельный Thread оправдан для долгоживущего фонового цикла или потока с особыми настройками вроде STA. У async есть и цена: объект состояния, поэтому на горячих путях применяют ValueTask.
Где спотыкаются
Говорят, что await запускает код в новом потоке. До первой незавершённой операции метод идёт синхронно, а продолжение планируется туда, куда укажет контекст.
Смешивают синхронное и асинхронное через .Result или .Wait(). В UI-приложении и старом ASP.NET это классический дедлок на SynchronizationContext, в ASP.NET Core контекста нет, зато голодает пул потоков.
Пишут async void вне обработчиков событий. Исключение из такого метода не поймать через try у вызывающего, и оно может уронить процесс.