Підрозділ 17.2
Типи повернення async-методів
17.2. Типи повернення async методів Асинхронні методи в C можуть повертати чотири типи: void , Task , Task<T і ValueTask<T . Вибір між ними — не питання смаку: кожен тип має своє призначення, обмеження та вплив
17.2. Типи повернення async-методів
Асинхронні методи в C# можуть повертати чотири типи: void, Task, Task<T> і ValueTask<T>. Вибір між ними — не питання смаку: кожен тип має своє призначення, обмеження та вплив на продуктивність. Правильний вибір типу повернення — ознака грамотного асинхронного коду.

async void — тільки для обробників подій
async void — найнебезпечніший тип повернення. Метод нічого не повертає, тому викликаючий код не може ні отримати результат, ні дочекатися завершення, ні перехопити виняток через try/catch.
Єдиний законний сценарій використання async void — обробники подій, де підпис методу встановлений зовнішнім інтерфейсом і не може бути змінений:
Ключове правило: ніколи не використовуйте async void поза обробниками подій. async void-метод не можна очікувати, не можна обробити його виняток ззовні, не можна перевірити його стан. Це «запустив і забув» у найгіршому сенсі.
async Task — операція без результату
async Task — стандартний тип повернення для асинхронних методів, що не повертають значення. На відміну від async void, Task дозволяє:
- очікувати завершення через
await - перехоплювати винятки через
try/catchнавколоawait - передавати завдання у
Task.WhenAllта інші комбінатори
async Task завжди кращий за async void там, де підпис методу під вашим контролем. Поверніть Task — і ваш код стає тестованим, перехоплюваним і сумісним з усіма async-комбінаторами.
async Task — операція з результатом
Task<T> — тип повернення для асинхронних методів, що обчислюють або отримують значення. Оператор await «розгортає» Task і повертає значення типу T. Це найпоширеніший тип для реальних асинхронних операцій:
Зверніть на return у async Task<T>: ви повертаєте значення типу T, а не Task<T>. Компілятор автоматично «загортає» його у Task<T>. Це симетрично до того, як await «розгортає» Task<T> назад до T.
ValueTask — для hot path без алокації
ValueTask<T> — оптимізований тип для сценаріїв, де метод часто завершується синхронно (без реального очікування). Різниця з Task<T>: якщо результат вже відомий і очікування не потрібне, ValueTask<T> не виділяє пам'яті у heap, оскільки є структурою (struct), а не класом.
ValueTask<T> слід використовувати лише коли є вимірювані докази, що алокація Task<T> є вузьким місцем (hot path, мільйони викликів). В усіх інших випадках — Task<T> є правильним вибором: він простіший, безпечніший і добре оптимізований у .NET.
Важливе обмеження: ValueTask<T> можна await-ати лише один раз. Якщо вам потрібно зберегти завдання у змінну і очікувати кілька разів — використовуйте Task<T>.
Task.FromResult і Task.CompletedTask — синхронні обгортки
Іноді потрібно повернути Task<T> або Task з методу, де фактична робота виконується синхронно. Найпоширенісний сценарій — реалізація інтерфейсу або тест-дублер:
Task.FromResult<T>(value) створює Task, що вже завершений зі значенням — без жодних алокацій стейт-машини. Task.CompletedTask — синглтон-Task для Task-методів без значення, що не вимагає реальної асинхронності. Обидва широко використовуються при реалізації інтерфейсів, заглушок і тест-дублерів.
Зведена таблиця типів повернення
| Тип | Коли використовувати | Awaitable | Перехоплення помилок | Алокація |
|---|---|---|---|---|
async void |
Тільки EventHandler | Ні | Ні (глобально) | Немає |
async Task |
Операція без результату | Так | Так | Task |
async Task<T> |
Операція з результатом | Так | Так | Task |
async ValueTask<T> |
Hot path з частим sync-шляхом | Так (1 раз) | Так | Немає (sync) |
Правило вибору: за замовчуванням — Task або Task<T>. async void — лише для EventHandler. ValueTask<T> — тільки при підтвердженому профілюванням вузькому місці.