Проблема в том, что используемый вами конструктор Task
принимает делегат Action
.Когда вы говорите
var task = new Task(async () => { /* Do things */ });
, вы не создаете задачу, которая «делает что-то»;Вместо этого вы создали задачу, которая выполняет действие, которое возвращает задачу , и эта (внутренняя) задача - это то, что «делает вещи».Создание этой (внутренней) задачи очень быстро, так как делегат возвращает Task
в первый await
и завершается почти сразу.Поскольку делегатом является Action
, результирующий Task
фактически отбрасывается и больше не может использоваться для ожидания.
Когда вы вызываете await Task.WhenAll(tasks)
для своих внешних задач, вы толькоожидание создания внутренних задач (почти немедленных).Внутренние задачи продолжают выполняться после этого.
Существуют переопределения конструктора, которые позволяют вам делать то, что вы хотите, но ваш синтаксис будет немного более громоздким, в соответствии с ответом Пауло:
public static Task<Task> CreatePauseTask(int ms)
{
return new Task<Task>(async () =>
{
Console.WriteLine($"Start {ms} ms pause");
await Task.Delay(ms);
Console.WriteLine($"{ms} ms are elapsed");
});
}
Теперь у вас есть задача, которая делает то же самое, но на этот раз возвращает внутреннюю Task
.Чтобы дождаться внутренних задач, вы можете сделать следующее:
await Task.WhenAll(await Task.WhenAll(taskList));
Внутреннее ожидание возвращает список внутренних задач, а внешнее ожидание их.
Как уже упоминалось,хотя - создание незапущенных задач с использованием конструкторов - это область, в которой вы действительно должны находиться, только если у вас есть очень специфическое требование, когда более стандартная практика использования Task.Run()
или простого вызова методов, возвращающих задачу, не удовлетворяет требованию (котороебудет делать 99,99% времени).
Большинство из этих Task
конструкторов были созданы еще до того, как асинхронное ожидание существовало, и, вероятно, все еще существуют по старым причинам.
Редактировать : что также может помочь, так это увидеть подписи двух делегатов, которые лямбда-нотация C # позволяет нам - обычно удобно - пропустить.
Для new Task(async () => { /* Do things */ })
у нас есть
async void Action() { }
(здесь основной проблемой является void
).
Для new Task<Task>(async () => { /* Do things */ })
у нас есть
async Task Function() { }
Обе лямбды синтаксически идентичны, но семантическиу разные.