Задача против барьера

Итак, моя проблема заключается в следующем: у меня есть список элементов для обработки, и я хотел бы обрабатывать элементы параллельно, а затем фиксировать обработанные элементы.

Класс барьера в C# позволит мне сделать это — я могу запускать потоки параллельно для обработки списка элементов, и когда вызывается SignalAndWait и все участники сталкиваются с барьером, я могу зафиксировать обработанные элементы.

Класс Task также позволит мне сделать это — при вызове Task.WaitAll я могу дождаться завершения всех задач и зафиксировать обработанные элементы. Если я правильно понимаю, каждая задача будет выполняться в своем собственном потоке, а не в нескольких параллельных задачах в одном потоке.

  1. Правильно ли я понимаю оба варианта использования проблемы?
  2. Есть ли преимущество между одним перед другим?
  3. Чем гибридное решение лучше (барьер и задачи?).

person Pr0ph3t    schedule 17.05.2015    source источник
comment
Никогда не знал о Barrier. После прочтения, возможно, вам больше нравится TPL Dataflow?   -  person MickyD    schedule 17.05.2015
comment
Когда вы обрабатываете свои данные, вы выполняете работу, связанную с вводом-выводом, или работу, связанную с процессором?   -  person Yuval Itzchakov    schedule 17.05.2015


Ответы (2)


Правильно ли я понимаю оба варианта использования проблемы?

Я думаю, у вас неправильное представление о классе Barrier. В документах говорится:

Барьер — это определяемый пользователем примитив синхронизации, который позволяет нескольким потокам (известным как участники) одновременно работать над алгоритмом поэтапно.

Барьер — это примитив синхронизации. Сравнивать его с единицей работы, которая может быть вычислена параллельно, такой как Task, некорректно.

Барьер может подать сигнал всем потокам подождать, пока все остальные не завершат некоторую работу, и проверить эту работу. Сам по себе он не имеет возможностей для параллельных вычислений и не использует потоковую модель.

Есть ли преимущество между одним перед другим?

Что касается вопроса 1, вы видите, что это не имеет значения.

Чем гибридное решение лучше (барьер и задачи?).

В вашем случае я не уверен, что это вообще нужно. Если вы просто хотите параллельно выполнять вычисления с привязкой к ЦП для набора элементов, у вас есть Parallel.ForEach именно для этой цели. Он разделит перечисляемое и вызовет их параллельно и заблокирует, пока вся коллекция не будет вычислена.

person Yuval Itzchakov    schedule 17.05.2015
comment
Отличный ответ. Я понимаю разницу, а что, если я скажу вам, что использую EF, например, для извлечения информации и выполнения вычислений, а затем после обработки - я буду фиксировать обработанные записи. В вашем примере я бы получил элементы для обработки и запускал расчеты, используя Parallel.foreach? - person Pr0ph3t; 17.05.2015
comment
@Pr0ph3t Да. Я бы запросил данные из EF (синхронно или асинхронно, в зависимости от того, что вы выберете), и как только данные будут загружены в память, я бы использовал Parallel.ForEach для выполнения этих вычислений. Вы также можете изучить PLINQ. которые могут соответствовать вашим потребностям. - person Yuval Itzchakov; 17.05.2015
comment
Похоже, с Parallel.ForEach и EF могут быть и другие проблемы, но это уже другая тема. - person Pr0ph3t; 17.05.2015
comment
@ Pr0ph3t Вы не должны использовать Parallel.ForEach для части запроса вашей операции! Используйте его после запроса данных. Если вам нужно одновременно обращаться к базе данных, изучите асинхронный API, предоставляемый EF. - person Yuval Itzchakov; 17.05.2015

Я не отвечаю прямо на ваш вопрос, потому что я думаю, что работа с барьерами и задачами просто делает ваш код более сложным, чем он должен быть.

Я бы предложил использовать для этого Microsoft Reactive Framework — NuGet «Rx-Main» — так как это делает всю проблему очень простой.

Вот код:

var query =
    from item in items.ToObservable()
    from processed in Observable.Start(() => processItem(item))
    select new { item, processed };

query
    .ToArray()
    .Subscribe(processedItems =>
    {
        /* commit the processed items */
    });

Запрос превращает список элементов в наблюдаемое, а затем обрабатывает каждый элемент, используя Observable.Start(...). Это оптимально запускает новые потоки по мере необходимости. .ToArray() берет последовательность отдельных результатов и превращает ее в единый массив результатов. Затем метод .Subscribe(...) позволяет обрабатывать результаты.

Код намного проще, чем использование задач или барьеров.

person Enigmativity    schedule 17.05.2015
comment
Но действительно ли ему нужны данные в потоковом деле? Точно сказать не могу. Кроме того, я не понимаю, как работа с задачами или Parallel.ForEach менее сложна, чем Rx? - person Yuval Itzchakov; 17.05.2015
comment
@YuvalItzchakov - Что вы подразумеваете под потоковой передачей? - person Enigmativity; 17.05.2015
comment
Rx будет передавать выходные данные на Subscribe по завершении запроса. - person Yuval Itzchakov; 17.05.2015
comment
@YuvalItzchakov - .ToArray() соберет все значения и передаст их все сразу. Этого хотел ОП. - person Enigmativity; 17.05.2015