Глава.27 .Асинхронные.вычислительные.операции
Если у вас есть выбор между
For
и
ForEach
, лучше используйте цикл
For
, так
как он работает быстрее.
Если вам нужно выполнить несколько методов, можно сделать это последова-
тельно — например, вот так:
// Один поток выполняет методы по очереди
Method1();
Method2();
Method3();
Также возможно параллельное выполнение:
// Потоки из пула выполняют методы одновременно
Parallel.Invoke(
() => Method1(),
() => Method2(),
() => Method3());
Все методы класса
Parallel
заставляют вызывающий поток принимать участие
в их обработке. Это хорошо с точки зрения расходования ресурсов, так как вызыва-
ющий поток не блокируется, ожидая выполнения работы потоками пула. Впрочем,
если выполнение вызывающего потока будет закончено до того, как потоки из пула
выполнят свою часть, вызывающий поток приостанавливается до их завершения,
что тоже неплохо, так как обеспечивает семантику, аналогичную применению
цикла
for
или
foreach
. Выполнение вызывающего потока не возобновляется, пока
не будет завершена вся работа. Если какая-либо операция станет источником не-
обработанного исключения, вызванный вами метод
Parallel
выдаст исключение
AggregateException
.
Разумеется, это не значит, что все циклы
for
в своем коде следует заменить вы-
зовами
Parallel.For
, а циклы
foreach
— вызовами
Parallel.ForEach
. Вызовы
Parallel
базируются на предположении, что рабочие элементы без проблем смогут
выполняться параллельно. Значит, для заданий, которые должны выполняться
последовательно, вызов этого метода не имеет смысла. Следует также избегать ра-
бочих элементов, вносящих изменения в любые совместно используемые данные,
так как при одновременном управлении несколькими потоками эти данные могут
оказаться поврежденными. Обычно эта проблема решается в рамках синхронизации
потоков блокированием фрагментов кода, в которых реализуется доступ к данным.
Однако так как после этого доступ к данным в каждый момент времени сможет по-
лучать только один поток, теряется преимущество одновременного обслуживания
множества элементов.
Кроме того, методы класса
Parallel
потребляют много ресурсов — приходится
выделять память под делегаты, которые вызываются по одному для каждого рабочего
элемента. При наличии множества рабочих элементов, которые могут обслуживать-
ся разными потоками, можно получить рост производительности. К тому же если
каждый элемент выполняет много работы, на снижение производительности из-за
вызова делегатов можно не обращать внимания. Проблемы начинаются в случае,
773
Методы.For,.ForEach.и.Invoke.класса.Parallel
когда методы класса
Parallel
применяются к небольшому числу рабочих элементов
или же к элементам, обслуживание которых происходит очень быстро.
Следует упомянуть, что для методов
For
,
ForEach
и
Invoke
класса
Parallel
существуют перегруженные версии, принимающие объект
ParallelOptions
. Вот
как он выглядит:
public class ParallelOptions{
public ParallelOptions();
// Допускает отмену операций
public CancellationTokenCancellationToken { get; set; }
// По умолчанию CancellationToken.None
// Позволяет задать максимальное количество рабочих
// элементов, выполняемых одновременно
public Int32MaxDegreeOfParallelism { get; set; }
// По умолчанию -1 (число доступных процессоров)
// Позволяет выбрать планировщика заданий
public TaskSchedulerTaskScheduler { get; set; }
// По умолчанию TaskScheduler.Default
}
Существуют перегруженные версии и для методов
For
и
ForEach
, позволяющие
передавать три делегата:
Делегат локальной инициализации задания (
localInit
) для каждой выпол-
няемой задачи вызывается только один раз — перед получением команды на
обслуживание рабочего элемента.
Делегат
body
вызывается один раз для каждого элемента, обслуживаемого уча-
ствующими в процессе потоками.
Делегат локального состояния каждого потока (
localFinally
) вызывается
один раз для каждого задания, после того как оно обслужит все переданные ему
рабочие элементы. Также он вызывается, если код делегата
body
становится ис-
точником необработанного исключения.
Следующий пример кода демонстрирует использование этих трех делегатов на
примере суммирования байтов для всех файлов, содержащихся в каталоге:
private static Int64 DirectoryBytes(String path, String searchPattern,
SearchOptionsearchOption) {
var files = Directory.EnumerateFiles(path, searchPattern, searchOption);
Int64 masterTotal = 0;
ParallelLoopResult result = Parallel.ForEach(
files,
() => { // localInit: вызывается в момент запуска задания
продолжение
774
Do'stlaringiz bilan baham: |