Ответ
Ключевое слово await компилируется в конечный автомат (state machine), реализованный компилятором как структура, реализующая интерфейс IAsyncStateMachine. Этот автомат разбивает асинхронный метод на части и управляет возобновлением выполнения после завершения асинхронной операции.
Как это работает:
- Компилятор создаёт приватную вложенную структуру (
<MethodName>d__X), которая хранит:- Состояние метода (
state, обычно-1,0,1,2...). - Локальные переменные исходного метода (превращаются в поля структуры).
TaskAwaiter(или другойawaiter) для ожидания задачи.AsyncTaskMethodBuilderдля управления созданием и завершением возвращаемойTask.
- Состояние метода (
- Вся логика метода перемещается в метод
MoveNext()этой структуры. - При встрече
await:- Если задача уже завершена (
IsCompleted == true), выполнение продолжается синхронно. - Если задача не завершена, состояние (
state) сохраняется, метод подписывается на уведомление о завершении задачи черезbuilder.AwaitUnsafeOnCompleted(...), и управление возвращается вызывающему коду. - При завершении задачи вызывается
MoveNext()снова, восстанавливается контекст и выполняется код послеawait.
- Если задача уже завершена (
Пример преобразования: Исходный код:
public async Task<int> GetDataAsync()
{
Console.WriteLine("Start");
int result = await FetchFromNetworkAsync();
Console.WriteLine("Result: " + result);
return result + 1;
}
Упрощённый вид сгенерированного кода (концептуально):
private struct <GetDataAsync>d__0 : IAsyncStateMachine
{
public int state;
public AsyncTaskMethodBuilder<int> builder;
private int result; // Локальная переменная стала полем
private TaskAwaiter<int> awaiter;
void MoveNext()
{
int finalResult;
try
{
if (state == 0) // Первый вход в метод
{
Console.WriteLine("Start");
awaiter = FetchFromNetworkAsync().GetAwaiter();
if (!awaiter.IsCompleted)
{
state = 1; // Устанавливаем состояние для точки возобновления
builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
return; // Выход, управление возвращается вызывающему
}
}
// Сюда попадаем при возобновлении (state == 1) или если задача завершилась синхронно
result = awaiter.GetResult(); // Получаем результат асинхронной операции
Console.WriteLine("Result: " + result);
finalResult = result + 1;
}
catch (Exception ex)
{
builder.SetException(ex); // Проброс исключения в возвращаемую Task
return;
}
builder.SetResult(finalResult); // Успешное завершение Task
}
}
Почему это важно понимать? Это объясняет накладные расходы на создание объекта состояния, важность использования ConfigureAwait(false) в библиотечном коде (чтобы избежать лишних захватов контекста синхронизации) и то, почему async/await — это не потоки, а модель на основе продолжений (continuations).