Ключевые слова 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
для управления состоянием выполнения. Этот механизм позволяет избежать блокировки потоков и эффективно работать с асинхронным кодом.