Porady: zapisywanie równoległej pętli For ze zmiennymi lokalnymi wątku

W tym przykładzie pokazano, jak używać zmiennych lokalnych wątków do przechowywania i pobierania stanu w każdym osobnym zadaniu utworzonym przez pętlę For . Korzystając z danych lokalnych wątków, można uniknąć obciążenia związanego z synchronizacją dużej liczby dostępu do stanu udostępnionego. Zamiast zapisywać w zasobie udostępnionym w każdej iteracji, obliczasz i przechowujesz wartość do momentu ukończenia wszystkich iteracji zadania. Następnie możesz napisać wynik końcowy raz do zasobu udostępnionego lub przekazać go do innej metody.

Przykład

Poniższy przykład wywołuje metodę For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) , aby obliczyć sumę wartości w tablicy zawierającej milion elementów. Wartość każdego elementu jest równa jego indeksowi.

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

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

        // Use type parameter to make subtotal a long, not an int
        Parallel.For<long>(0, nums.Length, () => 0,
            (j, loop, subtotal) =>
            {
                subtotal += nums[j];
                return subtotal;
            },
            subtotal => Interlocked.Add(ref total, subtotal));

        Console.WriteLine("The total is {0:N0}", total);
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}
'How to: Write a Parallel.For Loop That Has Thread-Local Variables

Imports System.Threading
Imports System.Threading.Tasks

Module ForWithThreadLocal

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

        ' Use type parameter to make subtotal a Long type. Function will overflow otherwise.
        Parallel.For(Of Long)(0, nums.Length, Function() 0, Function(j, [loop], subtotal)
                                                                subtotal += nums(j)
                                                                Return subtotal
                                                            End Function, Function(subtotal) Interlocked.Add(total, subtotal))

        Console.WriteLine("The total is {0:N0}", total)
        Console.WriteLine("Press any key to exit")
        Console.ReadKey()
    End Sub

End Module

Pierwsze dwa parametry każdej For metody określają początkowe i końcowe wartości iteracji. W tym przeciążeniu metody trzeci parametr to miejsce inicjowania stanu lokalnego. W tym kontekście stan lokalny oznacza zmienną, której okres istnienia rozciąga się od tuż przed pierwszą iterację pętli w bieżącym wątku do tuż po ostatniej iteracji.

Typ trzeciego parametru to gdzie TResult jest typem Func<TResult> zmiennej, która będzie przechowywać stan wątku lokalnego. Jego typ jest definiowany przez argument typu ogólnego podany podczas wywoływania metody ogólnej For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) , która w tym przypadku to Int64. Argument typu informuje kompilator o typie zmiennej tymczasowej, która będzie używana do przechowywania stanu wątku lokalnego. W tym przykładzie wyrażenie () => 0 (lub Function() 0 w Visual Basic) inicjuje zmienną lokalną wątku na zero. Jeśli argument typu ogólnego jest typem referencyjnym lub typem wartości zdefiniowanej przez użytkownika, wyrażenie będzie wyglądać następująco:

() => new MyClass()  
Function() new MyClass()  

Czwarty parametr definiuje logikę pętli. Musi to być wyrażenie delegowane lub lambda, którego podpis znajduje się Func<int, ParallelLoopState, long, long> w języku C# lub Func(Of Integer, ParallelLoopState, Long, Long) Visual Basic. Pierwszy parametr to wartość licznika pętli dla tej iteracji pętli. Drugi to ParallelLoopState obiekt, który może służyć do dzielenia pętli. Ten obiekt jest dostarczany przez Parallel klasę do każdego wystąpienia pętli. Trzeci parametr jest zmienną wątkowo-lokalną. Ostatnim parametrem jest zwracany typ. W tym przypadku typem jest Int64 to, że jest to typ określony w argumencie For typu. Ta zmienna ma nazwę subtotal i jest zwracana przez wyrażenie lambda. Wartość zwracana jest używana do inicjowania subtotal każdej kolejnej iteracji pętli. Możesz również traktować ten ostatni parametr jako wartość przekazywaną do każdej iteracji, a następnie przekazać go do delegata po zakończeniu localFinally ostatniej iteracji.

Piąty parametr definiuje metodę, która jest wywoływana raz, po zakończeniu wszystkich iteracji w określonym wątku. Typ argumentu wejściowego ponownie odpowiada argumentowi For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) typu metody i typowi zwróconym przez wyrażenie lambda treści. W tym przykładzie wartość jest dodawana do zmiennej w zakresie klasy w bezpieczny sposób przez wywołanie Interlocked.Add metody . Korzystając ze zmiennej wątkowo-lokalnej, unikaliśmy zapisywania w tej zmiennej klasy w każdej iteracji pętli.

Aby uzyskać więcej informacji na temat używania wyrażeń lambda, zobacz Wyrażenia lambda w plINQ i TPL.

Zobacz też