Параллельно для каждого или любой альтернативы для параллельного цикла? - PullRequest
0 голосов
/ 26 сентября 2019

У меня есть этот код

Lines.ToList().ForEach(y =>
{
    globalQueue.AddRange(GetTasks(y.LineCode).ToList());
});

Так что для каждой строки в моем списке строк я получаю задачи, которые добавляю в глобальную производственную очередь.Я могу иметь 8 строк.Каждый запрос на получение задачи GetTasks(y.LineCode) занимает 1 минуту.Я хотел бы использовать параллелизм, чтобы быть уверенным, что я запрашиваю свои 8 вызовов одновременно, а не один за другим.

Что мне делать?

Используя другой цикл ForEach или другой метод расширения?Есть ли ForEachAsync?Сделать запрос GetTasks сам асинхронным?

Ответы [ 2 ]

2 голосов
/ 26 сентября 2019

Параллелизм не параллелизм.Параллельность не асинхронность.Параллельный запуск нескольких медленных запросов не заставит их работать быстрее, скорее наоборот.Это разные проблемы и требуют совершенно разных решений.Без конкретной проблемы можно дать только общий совет.

Параллелизм - обработка массива элементов размером 800К

Параллелизм означает параллельную обработку тонны данных с использованием нескольких ядер.Для этого вам нужно разделить ваши данные и передать каждый раздел «работнику» для обработки.Чтобы добиться максимальной производительности, вам нужно свести к минимуму общение между работниками и синхронизацию, иначе ваши работники будут тратить процессорное время, ничего не делая.Это значит, что глобальное обновление очереди не требуется.

Если у вас много строк или если обработка строк связана с ЦП, вы можете использовать PLINQ для ее обработки:

var query = from y in lines.AsParallel()
            from t in GetTasks(y.LineCode)
            select t;

var theResults=query.ToList();

Вот и все,Нет необходимости синхронизировать доступ к очереди, либо с помощью блокировки, либо с помощью одновременной коллекции.Это будет использовать все доступные ядра, хотя.Вы можете добавить WithDegreeOfParallelism(), чтобы уменьшить количество ядер, используемых во избежание зависания

Параллелизм - вызов 2000 серверов

Параллельность с другой сторонызначит делать несколько разных вещей одновременно.Никакого разделения не происходит.

Например, если бы мне пришлось запрашивать данные мониторинга на 8 или 2000 серверах (правдивая история), я бы не использовал Parallel или PLINQ.С одной стороны, Parallel и PLINQ используют все доступные ядра.В этом случае, хотя они ничего не будут делать, они просто будут ждать ответов.Классы параллелизма также не могут обрабатывать асинхронные методы, потому что в этом нет никакого смысла - они не предназначены для ожидания ответов.

Очень быстрым и грязным решением было бы запустить несколько задач и ждатьдля их возврата, например:

var tasks=lines.Select(y=>Task.Run(()=>GetTasks(y.LineCode));
//Array of individual results
var resultsArray=await Task.WhenAll(tasks);

//flatten the results
var resultList=resultsArray.SelectMany(r=>r).ToList();

Это запустит все запросы сразу.Network Security не нравились одновременные запросы 2000 года, поскольку это выглядело как хакерская атака и вызвало небольшое наводнение сети.

Параллелизм с потоком данных

Мы можем использовать библиотеку потоков данных TPL и, например, ActionBlock или TransformBlock для выполнения запросов с контролируемой степенью параллелизма:

var options=new ExecutionDataflowBlockOptions { 
                    MaxDegreeOfParallelism = 4 ,
                    BoundedCapacity=10,
            };
var spamBlock=new TransformManyBlock<Line,Result>(
                               y=>GetTasks(y.LineCode),
                               options);
var outputBlock=new BufferBlock<Result>();
spamBlock.LinkTo(outputBlock);

foreach(var line in lines)
{
    await spamBlock.SendAsync(line);
}
spamBlock.Complete();
//Wait for all 4 workers to finish
await spamBlock.Completion;

По завершении spamBlock результаты можно найти в outputBlock.Установив BoundedCapacity, я гарантирую, что цикл отправки будет ждать, если во входной очереди spamBlock будет слишком много необработанных сообщений.

ActionBlock также может обрабатывать асинхронные методы.Предполагая, что GetTasksAsync возвращает Task<Result[]>, мы можем использовать:

var spamBlock=new TransformManyBlock<Line,Result>(
                               y=>GetTasksAsync(y.LineCode),
                               options);
2 голосов
/ 26 сентября 2019

Вы можете использовать Параллельный Foreach :

        Parallel.ForEach(Lines, (line) =>
        {
             globalQueue.AddRange(GetTasks(line.LineCode).ToList());
        });

Цикл Parallel.ForEach работает как цикл Parallel.For.Цикл разделяет исходную коллекцию и планирует работу в нескольких потоках в зависимости от системной среды.Чем больше процессоров в системе, тем быстрее работает параллельный метод.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...