Будет ли компилятор C# генерировать машину состояний (state machine) для метода, если в нем нет ключевых слов async/await?

«Будет ли компилятор C# генерировать машину состояний (state machine) для метода, если в нем нет ключевых слов async/await?» — вопрос из категории C# Core, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Краткий ответ: Нет, не будет. Машина состояний генерируется исключительно при использовании модификатора async и хотя бы одного оператора await внутри метода. Если async/await отсутствуют, метод компилируется как обычный синхронный или как метод, возвращающий Task напрямую.

Детали и сравнение:

Машина состояний — это специальный сгенерированный компилятором класс, который позволяет приостанавливать выполнение асинхронного метода на операторе await и затем возобновлять его, сохраняя локальные переменные и текущую позицию в коде.

// Пример 1: Метод БЕЗ машины состояний.
// Компилятор просто возвращает готовую задачу.
public Task<int> GetValueAsync_NoStateMachine()
{
    // Нет 'async', нет 'await'.
    // Часто используется для тривиальных или кэшированных результатов.
    return Task.FromResult(42);
}

// Пример 2: Метод С машиной состояний.
// Компилятор генерирует сложную структуру для управления асинхронностью.
public async Task<int> GetValueAsync_WithStateMachine()
{
    Console.WriteLine("Начало");
    // Точка приостановки. Компилятор разбивает метод на части.
    await Task.Delay(1000);
    // Это 'продолжение'. Выполнится после завершения Task.Delay.
    Console.WriteLine("Продолжение");
    return 42;
}

Что происходит под капотом во втором примере? Компилятор создает примерно следующую структуру:

// Упрощенное представление сгенерированного кода
[AsyncStateMachine(typeof(<GetValueAsync_WithStateMachine>d__1))]
public Task<int> GetValueAsync_WithStateMachine()
{
    var stateMachine = new <GetValueAsync_WithStateMachine>d__1();
    stateMachine.<>4__this = this;
    stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
    stateMachine.<>1__state = -1; // Начальное состояние
    stateMachine.<>t__builder.Start(ref stateMachine);
    return stateMachine.<>t__builder.Task;
}

private struct <GetValueAsync_WithStateMachine>d__1 : IAsyncStateMachine
{
    public int <>1__state; // -1, 0, 1... (состояние: ожидание, выполнение)
    public AsyncTaskMethodBuilder<int> <>t__builder; // Управляет Task'ом
    private TaskAwaiter <>u__1; // Ожидатель для Task.Delay

    void IAsyncStateMachine.MoveNext()
    {
        // Вся логика метода разбита на куски по 'await'
        if (<>1__state == 0) // Это состояние после первого await
        {
            // Код 'продолжения' (Console.WriteLine("Продолжение"); return 42;)
        }
        else
        {
            // Код до первого await (Console.WriteLine("Начало");)
            <>u__1 = Task.Delay(1000).GetAwaiter();
            if (!<>u__1.IsCompleted)
            {
                <>1__state = 0;
                <>t__builder.AwaitUnsafeOnCompleted(ref <>u__1, ref this);
                return; // Выход из метода! Поток освобождается.
            }
        }
        // ... обработка завершения
    }
}

Почему это важно? Машина состояний добавляет оверхед (аллокация объекта, больше IL-кода). Если метод может быть написан без async/await (просто возвращая Task), это будет эффективнее. Однако, async/await критически важны для читаемости и управления сложной асинхронной логикой.