Ключевые слова async и await в C# скрывают под собой сложную механику преобразования метода в конечный код на IL. Компилятор превращает асинхронные методы в конечный автомат, управляемый структурой AsyncStateMachine.
Развертывание async-метода
При компиляции асинхронный метод превращается в конечный автомат, который управляет состоянием выполнения. Рассмотрим пример:
public async Task<int> GetNumberAsync()
{
await Task.Delay(1000);
return 1;
}Компилятор развернет этот метод в класс, реализующий IAsyncStateMachine. Он будет выглядеть примерно так (упрощенно):
private struct GetNumberAsyncStateMachine : IAsyncStateMachine
{
public int State;
public AsyncTaskMethodBuilder<int> Builder;
private TaskAwaiter Awaiter;
public void MoveNext()
{
try
{
if (State == -1)
{
Awaiter = Task.Delay(1000).GetAwaiter();
if (!Awaiter.IsCompleted)
{
State = 0;
Builder.AwaitOnCompleted(ref Awaiter, ref this);
return;
}
}
Awaiter.GetResult();
Builder.SetResult(42);
}
catch (Exception ex)
{
Builder.SetException(ex);
}
}
public void SetStateMachine(IAsyncStateMachine stateMachine) { }
}Что делает await
Ключевое слово await заставляет компилятор развернуть асинхронный метод в IAsyncStateMachine, который управляет выполнением. await создает TaskAwaiter, который отслеживает состояние задачи.
Например, код:
await Task.Delay(1000);Будет превращен в:
TaskAwaiter awaiter = Task.Delay(1000).GetAwaiter();
if (!awaiter.IsCompleted)
{
State = 0;
Builder.AwaitOnCompleted(ref awaiter, ref this);
return;
}
awaiter.GetResult();Это позволяет задаче выполняться асинхронно без блокировки потока.
AsyncStateMachine и стек вызовов
Так как асинхронные методы используют AsyncStateMachine, их стек вызовов не сохраняется в стандартном виде. Вместо этого используется MoveNext(), который управляет состояниями выполнения. Это также объясняет, почему исключения в async void методах трудно перехватывать.
Во время компиляции async-методы превращаются в конечные автоматы, управляемые AsyncStateMachine. await использует TaskAwaiter для управления состоянием выполнения. Этот механизм позволяет избежать блокировки потоков и эффективно работать с асинхронным кодом.