Проблема полягає в тому, що ви використовуєте негенеричний 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. Це рідко, чого ви хочете, якщо і коли-небудь.