Partager via


Guide pratique pour écrire une boucle Parallel.For avec des variables Thread-Local

Cet exemple montre comment utiliser des variables locales de thread pour stocker et récupérer l’état dans chaque tâche distincte créée par une boucle For. En utilisant des données locales à un thread, vous pouvez éviter la lourdeur de la synchronisation de nombreux accès à un état partagé. Au lieu d’écrire dans une ressource partagée sur chaque itération, vous calculez et stockez la valeur jusqu’à ce que toutes les itérations de la tâche soient terminées. Vous pouvez ensuite écrire le résultat final une fois dans la ressource partagée ou le transmettre à une autre méthode.

Exemple :

L’exemple suivant appelle la méthode For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) pour calculer la somme des valeurs d’un tableau qui contient un million d’éléments. La valeur de chaque élément est égale à son index.

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 {total:N0}");
        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

Les deux premiers paramètres de chaque méthode For spécifient les valeurs d’itération de début et de fin. Dans cette surcharge de la méthode, le troisième paramètre est l’emplacement où vous initialisez votre état local. Dans ce contexte, l’état local signifie une variable dont la durée de vie s’étend juste avant la première itération de la boucle sur le thread actuel, à juste après la dernière itération.

Le type du troisième paramètre est un Func<TResult>TResult est le type de la variable qui stocke l’état local du thread. Son type est défini par l’argument de type générique fourni lors de l’appel de la méthode de For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) générique, qui dans ce cas est Int64. L’argument de type indique au compilateur le type de la variable temporaire qui sera utilisée pour stocker l’état local du thread. Dans cet exemple, l’expression () => 0 (ou Function() 0 en Visual Basic) initialise la variable locale de thread à zéro. Si l’argument de type générique est un type référence ou un type valeur défini par l’utilisateur, l’expression se présente comme suit :

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

Le quatrième paramètre définit la logique de boucle. Il doit s’agir d’une expression délégué ou lambda dont la signature est Func<int, ParallelLoopState, long, long> en C# ou Func(Of Integer, ParallelLoopState, Long, Long) en Visual Basic. Le premier paramètre est la valeur du compteur de boucle pour cette itération de la boucle. Le deuxième est un objet ParallelLoopState qui peut être utilisé pour sortir de la boucle ; cet objet est fourni par la classe Parallel à chaque occurrence de la boucle. Le troisième paramètre est la variable locale de thread. Le dernier paramètre est le type de retour. Dans ce cas, le type est Int64, car il s’agit du type que nous avons spécifié dans l’argument de type For. Cette variable est nommée subtotal et est retournée par l’expression lambda. La valeur de retour est utilisée pour initialiser subtotal sur chaque itération suivante de la boucle. Vous pouvez également considérer ce dernier paramètre comme une valeur passée à chaque itération, puis transmise au délégué localFinally lorsque la dernière itération est terminée.

Le cinquième paramètre définit la méthode appelée une seule fois, une fois que toutes les itérations sur un thread particulier ont été terminées. Le type de l’argument d’entrée correspond à l’argument type de la méthode For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) et au type retourné par l’expression lambda du corps. Dans cet exemple, la valeur est ajoutée à une variable à l'échelle de la classe d'une manière sécurisée pour les threads en appelant la méthode Interlocked.Add. En utilisant une variable locale de thread, nous avons évité d’écrire dans cette variable de classe sur chaque itération de la boucle.

Pour plus d’informations sur l’utilisation d’expressions lambda, consultez expressions lambda dans PLINQ et TPL.

Voir aussi