Проблема полягає в тому, що ви використовуєте негенеричний Task
клас, який не призначений для отримання результату. Отже, коли ви створюєте Task
екземпляр, передаючи делегат асинхронізації:
Task myTask = new Task(async () =>
... делегат трактується як async void
. Це async void
не є Task
, його не можна чекати, його виняток не вдається впоратися, і це джерело тисяч запитань, які задають розчаровані програмісти тут, у StackOverflow та інших місцях. Рішення полягає у використанні загального Task<TResult>
класу, оскільки ви хочете повернути результат, а результат - інший Task
. Отже, ви повинні створити Task<Task>
:
Task<Task> myTask = new Task<Task>(async () =>
Тепер, коли ви Start
зовнішній, Task<Task>
він буде завершений майже миттєво, оскільки його завдання полягає лише у створенні внутрішнього Task
. Тоді вам доведеться чекати і внутрішніх Task
. Ось як це можна зробити:
myTask.Start();
Task myInnerTask = await myTask;
await myInnerTask;
У вас є дві альтернативи. Якщо вам не потрібно явного посилання на внутрішнє, Task
тоді ви можете просто чекати зовнішнього Task<Task>
два рази:
await await myTask;
... або ви можете використовувати вбудований метод розширення Unwrap
який поєднує зовнішні та внутрішні завдання в одне:
await myTask.Unwrap();
Це розгортання відбувається автоматично, коли ви використовуєте набагато популярніші Task.Run
метод, який створює гарячі завдання, тому в Unwrap
даний час не використовується дуже часто.
Якщо ви вирішите, що ваш асинхронний делегат повинен повернути результат, наприклад a string
, вам слід оголосити myTask
змінну типовою Task<Task<string>>
.
Примітка: Я не схвалюю використання Task
конструкторів для створення холодних завдань. Оскільки практика, як правило, нахмурена, з причин, яких я не знаю, але, ймовірно, тому, що вона використовується так рідко, що має потенціал зненацька застати інших необізнаних користувачів / технічних працівників / рецензентів коду.
Загальна порада: Будьте уважні щоразу, коли ви надаєте делегата асинхронізації як аргумент методу. Цей метод в ідеалі повинен очікувати Func<Task>
аргументу (мається на увазі, що розуміє асинхронні делегати) або хоча б Func<T>
аргумент (мається на увазі, що принаймні створені Task
не будуть ігноруватися). У випадку нещасного випадку, що цей метод приймає Action
, ваш делегат буде розглядатися як async void
. Це рідко, чого ви хочете, якщо і коли-небудь.