Возвращается ли поток в пул потоков, если async-метод выполняется синхронно?

«Возвращается ли поток в пул потоков, если async-метод выполняется синхронно?» — вопрос из категории Многопоточность, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Нет, не возвращается. Если async-метод завершается синхронно (без реальной асинхронной работы), поток, который его вызвал, блокируется до полного завершения метода, как в обычном синхронном вызове.

Механизм работы: Ключевое слово async лишь позволяет компилятору переписать метод в состояниевую машину. Если эта машина не встречает незавершённую задачу (await), она выполняется целиком в текущем контексте синхронизации.

Примеры:

// Пример 1: Метод без await.
public async Task<int> CalculateSync()
{
    // Вся работа выполняется синхронно. Поток НЕ освобождается.
    int result = PerformHeavyComputation(); // Долгий расчёт
    return result; // Возвращается уже завершённая задача (Task.FromResult под капотом).
}

// Пример 2: Await на уже завершённой задаче.
public async Task<string> GetCachedValueAsync(string key)
{
    if (_cache.TryGetValue(key, out string value))
    {
        // Task.FromResult создаёт завершённую задачу. Await на ней мгновенен и синхронен.
        // Поток НЕ освобождается на время "ожидания".
        return value;
    }
    // Только здесь, при реальном асинхронном вызове, поток может быть освобождён.
    return await _database.GetValueAsync(key);
}

Когда поток ВОЗВРАЩАЕТСЯ в пул: Поток освобождается только в момент, когда await встречает задачу, которая ещё не завершена (например, await httpClient.GetAsync(...), await file.ReadAllTextAsync(...)). В этот момент состояние метода сохраняется, и поток возвращается в пул. Когда асинхронная операция завершится, продолжение метода может быть выполнено на любом свободном потоке из пула (или на исходном, если был контекст синхронизации, как в UI).

Вывод для разработчика: Не стоит помечать метод как async, если в нём нет реальных асинхронных операций. Это создаёт ненужные накладные расходы на создание объекта-машины состояний (AsyncStateMachine). Для простых синхронных методов, возвращающих Task, лучше использовать Task.FromResult.