Я пытаюсь обновить свой набор инструментов новыми инструментами, предлагаемыми C# 8, и один метод, который кажется особенно полезным, — это версия Task.WhenAll, которая возвращает IAsyncEnumerable. Этот метод должен передавать результаты задачи в потоковом режиме, как только они станут доступны, поэтому называть его WhenAll не имеет особого смысла. WhenEach звучит более уместно. Сигнатура метода:
Код: Выделить всё
public static IAsyncEnumerable WhenEach(Task[] tasks);
Этот метод можно использовать следующим образом:
Код: Выделить всё
var tasks = new Task[]
{
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 ProcessAsync(int result, int delay)
{
await Task.Delay(delay);
return result;
}
Ожидаемый результат:
Обработано: 5
Обработано: 4
Обработано: 1
Обработано: 3
Обработано: 2
Мне удалось написать базовую реализацию с использованием метода Task.WhenAny в цикле, но с этим подходом есть проблема:
Код: Выделить всё
public static async IAsyncEnumerable WhenEach(
Task[] tasks)
{
var hashSet = new HashSet(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, чтобы он работал достойно?
Подробнее здесь:
https://stackoverflow.com/questions/581 ... le-of-task