Ответ
Ключевая идея await — освободить текущий поток на время выполнения асинхронной операции (например, ввода-вывода), а не блокировать его в ожидании. Это достигается за счёт разделения метода на этапы и использования машины состояний, генерируемой компилятором.
Как это работает на примере:
public async Task<string> GetWebPageAsync(string url)
{
HttpClient client = new HttpClient();
// 1. Вызывается асинхронная операция. Она возвращает Task<string>.
Task<string> downloadTask = client.GetStringAsync(url);
// 2. Операция ещё не завершена. Ключевое слово `await` проверяет это.
// Текущий поток ОСВОБОЖДАЕТСЯ и может быть использован для других задач
// (например, обработки сообщений UI в WPF/WinForms).
// 3. Когда фоновая операция завершится (данные загружены с диска/сети),
// выполнение метода продолжится. Компилятор позаботится о возобновлении.
string pageData = await downloadTask;
// 4. Продолжение (rest of the method) выполняется.
// По умолчанию оно попытается вернуться в исходный контекст синхронизации
// (например, в UI-поток), если он был захвачен.
return ProcessData(pageData);
}
Технические детали "под капотом":
- State Machine: Компилятор преобразует async-метод в класс-машину состояний, которая хранит локальные переменные как поля и отслеживает, на каком
awaitостановилось выполнение. - Возврат управления: При встрече
awaitс незавершённой задачей (TaskилиTask<T>) метод возвращает управление вызывающему коду, возвращая тот жеTask. Сам поток при этом освобождается. - Продолжение (Continuation): К незавершённой задаче прикрепляется продолжение — колбэк, который содержит код оставшейся части метода. Когда фоновая операция (например, ответ от сетевой карты) завершится, продолжение будет запланировано на выполнение. Оно может быть запущено:
- В исходном контексте (
SynchronizationContext), если он был (например, UI-поток). - В потоке из пула потоков (
ThreadPool), если контекста нет (например, в консольном приложении или послеConfigureAwait(false)).
- В исходном контексте (
Важное уточнение: await не создаёт новый поток для ожидания. Ожидание ввода-вывода (I/O) происходит на уровне операционной системы с использованием механизмов вроде IOCP (IO Completion Ports). Поток не занят "спином" в ожидании, он свободен для полезной работы.