Что может возвращать асинхронный метод кроме Task
Что проверяют
Знаете ли вы, что async это не про тип возврата, а про способ компиляции метода, и какие типы компилятор умеет обслуживать. Заодно проверяют понимание цены: где аллокация лишняя, а где выигрыш от её экономии обернётся трудноуловимым багом.
Как отвечать
Помеченный async метод может возвращать:
TaskиTask<TResult>, обычный случай для любого публичного API;void, но только для обработчиков событий: иначе вызывающий не может ни дождаться результата, ни поймать исключение;ValueTaskиValueTask<TResult>, когда результат чаще готов сразу, например лежит в кэше, и аллокация задачи не нужна;IAsyncEnumerable<TResult>для метода сyield return, который потребляют черезawait foreach;- любой свой тип, помеченный атрибутом
AsyncMethodBuilder, за этим обычно и стоит вопрос про «кроме Task».
Стоит добавить, что метод без async тоже возвращает задачу, и часто это лучший вариант: если делать нечего, кроме как передать результат дальше, return inner; дешевле, чем сгенерированная машина состояний.
Про ValueTask назовите ограничения сами: ждать её можно один раз, хранить и ждать из двух мест нельзя, обращаться к Result до завершения нельзя. Нужно повторно, есть AsTask.
Где спотыкаются
Пишут async void не для обработчика события. Исключение из такого метода не ловится вокруг вызова, оно улетает в контекст синхронизации и роняет процесс, а сам вызов невозможно дождаться, поэтому тест остаётся зелёным при сломанной логике.
Ставят ValueTask всюду ради производительности. Выигрыш заметен только на горячем пути с частым синхронным завершением, а повторное ожидание одной и той же структуры даёт неопределённое поведение, которое проявится под нагрузкой.
Путают, что видит вызывающий. Внутри async Task<int> пишут return 5, и делают из этого вывод, что метод возвращает число. Отсюда же попытки вернуть задачу из блока using без await: ресурс освободится раньше, чем операция закончится.