Я пытаюсь обновить свой набор инструментов новыми инструментами, предлагаемыми C # 8 , и один метод, который кажется особенно полезным, - это версия Task.WhenAll
, которая возвращает IAsyncEnumerable
. Этот метод должен транслировать результаты задачи, как только они станут доступны, поэтому присвоение ему имени WhenAll
не имеет особого смысла. WhenEach
звучит более уместно. Сигнатура метода:
public static IAsyncEnumerable<TResult> WhenEach<TResult>(Task<TResult>[] tasks);
Этот метод можно использовать следующим образом:
var tasks = new Task<int>[]
{
ProcessAsync(1, 300),
ProcessAsync(2, 500),
ProcessAsync(3, 400),
ProcessAsync(4, 200),
ProcessAsync(5, 100),
};
await foreach (int result in WhenEach(tasks))
{
Console.WriteLine($"Processed: {result}");
}
static async Task<int> ProcessAsync(int result, int delay)
{
await Task.Delay(delay);
return result;
}
Ожидаемый результат:
Обработано: 5
Обработано: 4
Обработано: 1
Обработано: 3
Обработано: 2
Мне удалось написать базовую реализацию, используя метод Task.WhenAny
в цикле, но есть проблема с этим подходом:
public static async IAsyncEnumerable<TResult> WhenEach<TResult>(
Task<TResult>[] tasks)
{
var hashSet = new HashSet<Task<TResult>>(tasks);
while (hashSet.Count > 0)
{
var task = await Task.WhenAny(hashSet).ConfigureAwait(false);
yield return await task.ConfigureAwait(false);
hashSet.Remove(task);
}
}
Проблема заключается в производительности. Реализация Task.WhenAny
создает защитную копию предоставленного списка задач, поэтому повторный вызов в цикле приводит к O (n²) вычислительной сложности. Моя наивная реализация пытается обработать 10 000 задач. Перегрузка в моей машине составляет почти 10 секунд. Мне бы хотелось, чтобы этот метод был почти таким же быстродействующим, как и встроенный Task.WhenAll
, который может с легкостью обрабатывать сотни тысяч задач. Как можно улучшить метод WhenEach
, чтобы он работал прилично?