Потокобезопасность возврата доходности с помощью Parallel.ForEach()

Рассмотрим следующий пример кода, который создает перечислимую коллекцию целых чисел и обрабатывает ее параллельно:

using System.Collections.Generic;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        Parallel.ForEach(CreateItems(100), item => ProcessItem(item));
    }

    private static IEnumerable<int> CreateItems(int count)
    {
        for (int i = 0; i < count; i++)
        {
            yield return i;
        }
    }

    private static void ProcessItem(int item)
    {
        // Do something
    }
}

Гарантируется ли, что рабочие потоки, сгенерированные Parallel.ForEach(), получают разные элементы, или требуется какой-то механизм блокировки для увеличения и возврата i?


person Good Night Nerd Pride    schedule 10.06.2013    source источник
comment
Просто используйте Enumerable.Range там.   -  person It'sNotALie.    schedule 10.06.2013
comment
@newStackExchangeInstance: я думаю, это просто образец итератора.   -  person Dennis    schedule 10.06.2013
comment
@newStackExchangeInstance: И как Enumerable.Range() помогает мне с параллельной обработкой IEnumerable?   -  person Good Night Nerd Pride    schedule 10.06.2013
comment
Кстати, в этом конкретном случае, вероятно, было бы более разумно использовать Parallel.For().   -  person svick    schedule 10.06.2013


Ответы (2)


Parallel.ForEach<TSource>, когда TSource является IEnumerable<T>, создает разделитель для IEnumerable<T>, который включает собственный внутренний механизм блокировки, поэтому вам не нужно реализовывать в итераторе потокобезопасность.

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

  1. получает общий замок
  2. выполняет итерацию по источнику (откуда он был оставлен) для извлечения фрагмента элементов, сохраняя элементы в частном массиве.
  3. снимает блокировку, чтобы можно было выполнять другие запросы фрагментов.
  4. обслуживает рабочий поток из своего частного массива.

Как видите, прогон через IEnumerable<T> для целей разбиения является последовательным (доступ через общий замок), а разбиения обрабатываются параллельно.

person Eren Ersönmez    schedule 10.06.2013
comment
Нашел это через Google, сделал это, и VS сказал, что его можно просто связать с Parallel.ForEach(...). Возможно ли, что ForEach теперь имеет эту функциональность независимо от того, какой тип указан? - person Squirrelkiller; 16.08.2016
comment
@MarlonRegenhardt да, VS правильный. Эта функция упрощения называется выводом типа. Например, когда вы вводите Parallel.ForEach(myList, ...), компилятор C# может вывести параметр универсального типа (<TSource>), просматривая универсальный тип myList. - person Eren Ersönmez; 16.08.2016

TPL и PLINQ используют концепцию разделителей.

Partitioner — это тип, который наследует Partitioner<TSource> и служит для разделения исходного последовательность на число частей (или разделов). Встроенные разделители были разработаны для разделения исходной последовательности на непересекающиеся разделы.

person Dennis    schedule 10.06.2013
comment
Я думаю, что на самом деле это не отвечает на вопрос: является ли код в вопросе (который, на первый взгляд, вообще не использует Partitioner) потокобезопасным? - person svick; 10.06.2013
comment
@svick: вопрос в том, гарантируется ли, что рабочие потоки, сгенерированные Parallel.ForEach(), получат каждый отдельный элемент. Вопрос не в потокобезопасности итератора. - person Dennis; 10.06.2013