Понимание Task.WhenAll, код ведет себя не очевидным образом
Проблема следующая представьте что у нас вызовы GetTask и GetOtherTask являются долгими по времени, что бы ускорить время ожидания мы их объединяем и вызываем через Task.WhenAll, но проблема в том что вызов GetTask вызывается не 2 раза (по количеству элементов в коллекции что видно в коде) а целых 5, и как результат у нас происходит лишних 3 вызова.
namespace Example {
class Program {
static async Task Main(string[] args) {
var firstTask = GetOtherTask();
var secondTasks = Enumerable.Range(0,2).Select( async x=> {
return await GetTask(); /// <--- Сюда заходит 5 раз хотя должен только 2
});
var tasks = secondTasks.Union(new[] { firstTask });
await Task.WhenAll(tasks);
var result = tasks.Select(x => {
return x;
}).ToArray();
}
static async Task<int> GetTask() {
await Task.Delay(100);
return 0;
}
static async Task<int> GetOtherTask() {
await Task.Delay(100);
return 1;
}
}
}
Ответы (1 шт):
Проблема заключается в ленивости.
tasks - это на самом деле итератор.
- Первый раз он материализуется при передаче в
Task.WhenAll - Второй раз - при вызове
.ToArrayпосле.Select
Чтобы этого избежать, достаточно материализовать его один раз перед передачей в Task.WhenAll и передавать уже результат.
Также можно сразу материализовать var secondTasks = (...).ToArray()
Альтернативным решением может стать использование значения возвращаемого методом Task.WhenAll - массив значений переданных тасков:
var tasks = secondTasks.Union(new[] { firstTask });
var taskResults = await Task.WhenAll(tasks);
var result = taskResults.Select(x => {
return x;
}).ToArray();
В этом случае возможно не нужна последняя строчка с result.