Ответ
Нет, не возвращается. Если 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.
Ответ 18+ 🔞
Да ты посмотри, какая история интересная вырисовывается! Вот сидишь ты, пишешь свой async-метод, думаешь: "Щас я тут await вставлю — и поток сразу в пул вернётся, красота!" А нихуя подобного, оказывается.
Смотри, если твой async-метод внутри себя не натыкается на реально незавершённую задачу, то он ведёт себя как самый обычный синхронный кусок кода. Тот поток, который его вызвал, тупо встанет и будет ждать, пока всё внутри не отработает. Никакого волшебного освобождения потока не произойдёт.
Как это под капотом устроено:
Ключевое слово async — это просто команда компилятору: "Слушай, перепиши мне этот метод в ту самую хитрую состояниевую машину". А эта машина, если не видит перед await задачи, которая ещё в процессе, просто прогоняет весь код до конца в текущем потоке. И всё.
Вот, смотри на примерах, чтобы совсем понятно стало:
// Пример 1: Чисто синхронный метод под маской async.
public async Task<int> CalculateSync()
{
// Тут нет ни одного настоящего await!
// Поток зайдёт сюда и упрётся лбом в этот долгий расчёт.
int result = PerformHeavyComputation(); // Сидит, ждёт, пыхтит.
return result; // И вернёт уже готовую, завершённую таску.
}
// Пример 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 встречает задачу, которая ещё в работе, которая не завершена. Типа сетевого запроса, чтения с диска или любой другой операции, где есть ожидание. Вот тут состояние метода замораживается, поток освобождается и идёт делать другие дела. А когда операция дойдёт до ума, продолжение метода подхватит какой-нибудь свободный поток из пула (если, конечно, у тебя нет какого-нибудь контекста синхронизации, как в UI-приложении, где надо вернуться в тот же поток).
Вывод, который мозг выносит:
Не лепи async на метод просто так, для красоты. Если внутри нет реальной асинхронщины — это пустая трата ресурсов на создание этой самой состояниевой машины. Для синхронных методов, которые должны вернуть Task, есть Task.FromResult. Используй его и не выёбывайся.