Поделиться через


Практическое руководство: Как записать цикл Parallel.ForEach с переменными, локальными для отдельных частей.

В следующем примере показано, как написать метод ForEach, использующий локальные переменные секции. При выполнении цикла ForEach он делит свою исходную коллекцию на несколько секций. Каждая секция имеет собственную копию локальной переменной секции. Локальная переменная раздела аналогична локальной переменной потока , но несколько разделов могут выполняться в одном потоке.

Код и параметры в этом примере очень похожи на соответствующий метод For. Дополнительные сведения см. в разделе "Как написать цикл Parallel.For с Thread-Local переменными".

Чтобы использовать локальную переменную секции в цикле ForEach, необходимо вызвать одну из перегрузок метода, принимающую два параметра типа. Первый параметр типа, TSource, указывает тип исходного элемента, а второй параметр типа TLocalуказывает тип локальной переменной секции.

Пример

В следующем примере вызывается перегрузка Parallel.ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) для вычисления суммы массива из одного миллиона элементов. Эта перегрузка имеет четыре параметра:

  • source, являющийся источником данных. Он должен реализовать IEnumerable<T>. Источник данных в нашем примере — это объект IEnumerable<Int32>, содержащий один миллион элементов, возвращаемый методом Enumerable.Range.

  • localInitили функция, которая инициализирует локальную переменную раздела. Эта функция вызывается один раз для каждой секции, в которой выполняется операция Parallel.ForEach. В нашем примере наша локальная переменная раздела инициализируется до нуля.

  • body, Func<T1,T2,T3,TResult>, вызываемая параллельным циклом для каждой итерации цикла. Его идентификатор Func\<TSource, ParallelLoopState, TLocal, TLocal>. Вы предоставляете код для делегата, а цикл передает входные параметры, которые:

    • Текущий элемент IEnumerable<T>.

    • Переменная ParallelLoopState, которую можно использовать в коде делегата для проверки состояния цикла.

    • Локальная переменная раздела.

    Делегат возвращает локальную переменную секции, которая затем передается в следующую итерацию цикла, выполняемого в этой конкретной секции. Каждая секция цикла поддерживает отдельный экземпляр этой переменной.

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

  • localFinallyделегат Action<TLocal>, который вызывается Parallel.ForEach после завершения операций циклирования в каждом разделе. Метод Parallel.ForEach передает вашему делегату Action<TLocal> окончательное значение локальной переменной для данного раздела в этой части цикла, а вы предоставляете код, который выполняет необходимые действия для объединения результата из этого раздела с результатами из других частей. Этот делегат может вызываться одновременно несколькими задачами. Из-за этого в примере используется метод Interlocked.Add(Int32, Int32) для синхронизации доступа к переменной total. Так как тип делегата является Action<T>, возвращаемое значение отсутствует.

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Test
{
    static void Main()
    {
        int[] nums = Enumerable.Range(0, 1000000).ToArray();
        long total = 0;

        // First type parameter is the type of the source elements
        // Second type parameter is the type of the thread-local variable (partition subtotal)
        Parallel.ForEach<int, long>(
            nums, // source collection
            () => 0, // method to initialize the local variable
            (j, loop, subtotal) => // method invoked by the loop on each iteration
            {
                subtotal += j; //modify local variable
                return subtotal; // value to be passed to next iteration
            },
            // Method to be executed when each partition has completed.
            // finalResult is the final value of subtotal for a particular partition.
            (finalResult) => Interlocked.Add(ref total, finalResult));

        Console.WriteLine($"The total from Parallel.ForEach is {total:N0}");
    }
}
// The example displays the following output:
//        The total from Parallel.ForEach is 499,999,500,000
' How to: Write a Parallel.ForEach Loop That Has Thread-Local Variables

Imports System.Threading
Imports System.Threading.Tasks

Module ForEachThreadLocal
    Sub Main()

        Dim nums() As Integer = Enumerable.Range(0, 1000000).ToArray()
        Dim total As Long = 0

        ' First type parameter is the type of the source elements
        ' Second type parameter is the type of the thread-local variable (partition subtotal)
        Parallel.ForEach(Of Integer, Long)(nums, Function() 0,
                                           Function(elem, loopState, subtotal)
                                               subtotal += elem
                                               Return subtotal
                                           End Function,
                                            Sub(finalResult)
                                                Interlocked.Add(total, finalResult)
                                            End Sub)

        Console.WriteLine("The result of Parallel.ForEach is {0:N0}", total)
    End Sub
End Module
' The example displays the following output:
'       The result of Parallel.ForEach is 499,999,500,000

См. также