#c# #.net #multithreading #parallel-processing #task
#c# #.net #многопоточность #параллельная обработка #задача
Вопрос:
Наличие базового класса с 2 методами, подобными этому:
public void ReadAllData(List<Entity> entities){
//processing
foreach(Entity e in entities){
ReadSingleData(entities[i]);
}
//processing
}
public void ReadSingleData(Entity entity){
//processing
db.ReadFromDataBase();
//processing
}
Мне нужно ускорить чтение из базы данных, заполнение коллекций и т.д..
Сначала я использовал эту реализацию с библиотекой параллельных задач:
Action[] actions = new Action[entities.Count);
public void ReadAllData(List<Entity> entities){
//processing
for(int i = 0; i< entities.Count;i ){
actions[i] = new Action(() => ReadSingleData(entities[i]));
}
//processing
}
ParallelOptions op = new ParallelOptions();
op.MaxDegreeOfParallelism = 6; //number of logical cores
Parallel.Invoke(op, actions);
И я использовал методы async / await:
Task<bool>[] tasks = new Task<bool>[entities.Count];
public void ReadAllData(List<Entity> entities){
//processing
for(int i = 0; i< entities.Count;i ){
tasks[i] = ReadSingleData(entities[i]);
}
Task.WaitAll(tasks);
//processing
}
public async Task<bool> ReadSingleData(Entity entity){
//processing
await Task.Run(() =>
db.ReadFromDataBase();
});
//processing
return flag;
}
Это просто псевдокод. При реализации задачи я получаю ускорение примерно на 35-40% (примерно на 30 секунд). Мой вопрос в том, как параллельная библиотека работает внутри? Использует ли библиотека задачи, подобные моей второй реализации? Что может быть не так с первой реализацией?
Спасибо.
Комментарии:
1. Вы могли бы взглянуть на реализацию
Parallel.Invoke
в справочном источнике . «Если количество действий больше 10, мы автоматически используемParallel.For()
для обработки действий, а не стратегию «Задача за действие»2. Возможно, вы также захотите изучить поток данных TPL .
3. Возможно, стоит также рассмотреть реактивный фреймворк Microsoft. Затем вы можете писать
var query = from entity in entities.ToObservable() from flag in Observable.FromAsync(() => ReadSingleData(entity)) select flag; query.ToArray().Wait();
.
Ответ №1:
Разница в том, что Parallel.Invoke
в определенных случаях не создает задачу для каждого действия, запланированного к выполнению, и ведет себя аналогично Parallel.ForEach
. Более подробно, если количество задач превышает вашу степень параллелизма, которая либо явно указана (как в вашем случае), либо определяется количеством ядер, Parallel.Invoke
разбивает действия для вызова на пакеты, чтобы соответствовать требуемой степени параллелизма. Действия в одном пакете выполняются последовательно. Task.Run
Напротив, в подавляющем большинстве случаев выполнение расписывается в пуле потоков.
В случае вычислений, ограниченных процессором, Parallel.Invoke
логика выигрывает, поскольку она подразумевает эффективное использование доступных ресурсов с небольшим конфликтом. Операции с ограниченным вводом-выводом меняют ситуацию, поскольку теперь у вас много конфликтов ввода-вывода, которые могут остаться нераскрытыми в случае Parallel.Invoke
и когда пропускная способность ввода-вывода не может быть полностью использована одновременными Parallel.Invoke
действиями, число которых ограничено (другие операции, которые потенциально могут выполняться во время выполнения ввода-вывода, просто ожидают пакетного выполнения). Task.Run
в таких сценариях имеет преимущество, поскольку без верхнего предела параллелизма он может покрыть оставшуюся конкуренцию. Вот почему при определенных обстоятельствах это может увеличить производительность. Однако есть недостаток: неконтролируемое применение Task.Run
может привести к ситуациям, когда пул потоков перегружен задачами и должен порождать новые потоки для обслуживания входящих задач (когда количество запланированных задач ввода-вывода превышает пропускную способность ввода-вывода), что, в свою очередь, может привести к снижению производительности. Вот почему рекомендуемый способ работы с операциями, ограниченными вводом-выводом, заключается в использовании async/await
, поскольку он зависит от внутренних механизмов (таких как порты завершения ввода-вывода), которые обеспечивают оптимальное использование ввода-вывода.