Ответ
Ключевое слово async само по себе не создает новый поток. Оно лишь позволяет использовать await внутри метода. Механика выполнения выглядит так:
- До первого
await: Код выполняется синхронно в вызывающем потоке. - При встрече
await: Если ожидаемая задача (Task) еще не завершена, управление возвращается вызывающему коду. Сам поток при этом освобождается. - После завершения асинхронной операции: Оставшаяся часть метода (продолжение, или continuation) планируется на выполнение. Где именно оно выполнится, зависит от контекста синхронизации (
SynchronizationContext).
Пример, демонстрирующий смену потоков:
async Task FooAsync()
{
// Выполняется в исходном потоке (например, UI-потоке).
Console.WriteLine($"Before await. Thread ID: {Thread.CurrentThread.ManagedThreadId}");
// Приостановка. UI-поток освобождается, не блокируется.
await Task.Delay(1000);
// Продолжение может выполниться в ДРУГОМ потоке (например, из пула).
Console.WriteLine($"After await. Thread ID: {Thread.CurrentThread.ManagedThreadId}");
}
Важные контексты:
- UI-приложения (WPF, WinForms): Благодаря
SynchronizationContextпродолжение обычно выполняется в том же UI-потоке, что обеспечивает безопасный доступ к элементам управления. - ASP.NET Core и консольные приложения: Контекст синхронизации по умолчанию отсутствует. Продолжение выполняется в потоке из пула (
ThreadPool), который может быть любым.
Итог: Асинхронность — это не про создание потоков, а про эффективное использование существующих за счет освобождения их во время операций ввода-вывода (I/O).