Procédure : écrire une boucle Parallel.For avec des variables locales de thread
Cet exemple montre comment utiliser des variables de thread local pour stocker et récupérer l’état de chaque tâche créée par une boucle For. En utilisant des données de thread local, vous pouvez éviter la surcharge liée à la synchronisation d’un grand nombre d’accès à un état partagé. Au lieu d’écrire dans une ressource partagée à 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 une fois le résultat final 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 {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
Les deux premiers paramètres de chaque méthode For spécifient les valeurs d'itération initiale et finale. Dans cette surcharge de la méthode, le troisième paramètre indique où vous initialisez votre état local. Dans ce contexte, « état local » signifie une variable dont la durée de vie commence juste avant la première itération de la boucle sur le thread actuel et se termine juste après la dernière itération.
Le type du troisième paramètre est un délégué Func<TResult> où TResult
représente le type de la variable destinée à stocker l'état de thread local. Son type est défini par l’argument de type générique fourni au moment de l’appel de la méthode For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) générique, en l’occurrence Int64. L’argument de type indique au compilateur le type de la variable temporaire à utiliser pour stocker l’état de thread local. Dans cet exemple, l'expression () => 0
(ou Function() 0
dans Visual Basic) initialise la variable de thread local à zéro. Si l'argument de type générique est un type de référence ou un type de valeur défini par l'utilisateur, l'expression ressemble à ceci :
() => new MyClass()
Function() new MyClass()
Le quatrième paramètre définit la logique de la boucle. Il doit correspondre à un délégué ou à une expression 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 permet de quitter la boucle ; cet objet est fourni par la classe Parallel à chaque occurrence de la boucle. Le troisième paramètre est la variable de thread local. Le dernier paramètre est le type de retour. Dans ce cas, le type est Int64, car c’est ce 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
à chaque itération suivante de la boucle. Vous pouvez également considérer ce dernier paramètre comme une valeur transmise à chaque itération, puis communiquée au délégué localFinally
quand la dernière itération est terminée.
Le cinquième paramètre définit la méthode qui est appelée une fois, après la fin de toutes les itérations sur un thread particulier. Le type de l'argument d'entrée correspond de nouveau à l'argument de 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 à portée de classe d'une façon thread-safe en appelant la méthode Interlocked.Add. L'utilisation d'une variable de thread local nous a évité d'écrire dans cette variable de classe à chaque itération de la boucle.
Pour plus d’informations sur l’utilisation d’expressions lambda, consultez Expressions lambda en PLINQ et dans la bibliothèque parallèle de tâches.