Какие типы может возвращать async метод в C# и почему void плохо возвращать
Что проверяют
Знаете ли вы, что async это про машину состояний, а не про типы, и понимаете ли, чем опасен потерянный Task. Следом спрашивают, где async void всё-таки допустим и что возвращать в горячем пути, где заметна аллокация задачи.
Как отвечать
Перечислите допустимые типы и назначение каждого:
Task: метод без результата, но его можно ждать, компоновать черезTask.WhenAllи ловить его исключения;Task<T>: то же самое с результатом;ValueTaskиValueTask<T>: для путей, которые часто завершаются синхронно, например значение уже в кэше. Экономят аллокацию, но правила жёсткие: ждать ровно один раз, результат не читать повторно, объект не хранить;IAsyncEnumerable<T>: асинхронный итератор сyield return, потребляется черезawait foreach, токен отмены прокидывается атрибутом[EnumeratorCancellation];void: разрешён, но по сути только для обработчиков событий;- свой тип с атрибутом
[AsyncMethodBuilder], если нужен собственный builder.
Теперь про void. У такого метода нет объекта, за которым можно наблюдать. Вызывающий не дождётся завершения, отсюда гонки и тесты, зелёные просто потому, что ничего не дождались. Исключение не оседает в Task, а выбрасывается в контекст синхронизации, а там, где контекста нет (ASP.NET Core, консоль), уходит в пул потоков как необработанное и роняет процесс. Такой метод не обернуть внешним try/catch, не поставить в WhenAll, не отменить и не повторить.
Если нужно «выстрелить и забыть», возвращайте Task и явно решайте его судьбу: свой try/catch внутри, логирование ошибки, ссылка на задачу, чтобы её дождались при остановке.
Где спотыкаются
Считают async void просто «методом без возвращаемого значения» и путают его с async Task. Разница не в сигнатуре, а в том, кто получит исключение.
Ставят ValueTask везде ради экономии, а потом ждут одну и ту же переменную дважды или кладут её в поле. Это неопределённое поведение, и ловится оно плохо.
Подменяют ожидание блокировкой: .Result, .Wait(), GetAwaiter().GetResult(). В WPF и WinForms это классический дедлок, в ASP.NET Core просто съеденный поток пула и деградация под нагрузкой.