Mit Bezug auf die following overload der Parallel.ForEach
statische Erweiterungsmethode:
public static ParallelLoopResult ForEach<TSource, TLocal>(
IEnumerable<TSource> source,
Func<TLocal> localInit,
Func<TSource, ParallelLoopState, TLocal, TLocal> taskBody,
Action<TLocal> localFinally
)
In Ihrem speziellen Beispiel
Die Linie:
() => 0, // method to initialize the local variable
ist einfach eine Lambda (anonyme Funktion), die die konstante ganze Zahl Null zurückgibt.Dieses lambda wird als localInit
Parameter Parallel.ForEach
bestanden - da das Lambda eine ganze Zahl zurückgibt, hat es Func<int>
Typen und Typen TLocal
kann als int
vom Compiler (in ähnlicher Weise abgeleitet werden, TSource
kann von dem Typ der als Parameter übergebenen Sammlung gefolgert werden source
)
Der Rückgabewert (0) wird dann als 3. Parameter (subtotal
) an taskBody
Func
übergeben.
(j, loop, subtotal) =>
{
subtotal += nums[j]; //modify local variable (Bad idea, see comment)
return subtotal; // value to be passed to next iteration
}
Dieses zweite Lambda (bestanden taskBody
) wird N-mal genannt, wobei N die Anzahl der Elemente dieser Aufgabe durch die TPL Partitionierer zugewiesen: Dieser (0) den Anfangskeim für den Körper Schleife verwendet.
Jeder nachfolgende Aufruf der zweiten taskBody
lambda wird eine Lauf Teil Gesamt Berechnung für diese Aufgabe den neuen Wert von subTotal
, effektiv weitergeben. Nachdem alle der Aufgabe zugewiesenen Elemente hinzugefügt wurden, wird der dritte und letzte, localFinally
Funktionsparameter aufgerufen, der wiederum den letzten Wert subtotal
liefert, der von taskBody
zurückgegeben wird. Da mehrere dieser Aufgaben parallel ausgeführt werden, muss auch ein abschließender Schritt durchgeführt werden, um alle Teilergebnisse in die endgültige Gesamtsumme aufzuneh- men. Da jedoch mehrere gleichzeitige Aufgaben (in verschiedenen Threads) um die Variable grandTotal
konkurrieren können, ist es wichtig, dass Änderungen daran threadsicher vorgenommen werden.
(Ich habe geändert Namen der MSDN-Variablen, um es klar)
long grandTotal = 0;
Parallel.ForEach(nums, // source collection
() => 0, // method to initialize the local variable
(j, loop, subtotal) => // method invoked by the loop on each iteration
subtotal + nums[j], // value to be passed to next iteration subtotal
// The final value of subtotal is passed to the localFinally function parameter
(subtotal) => Interlocked.Add(ref grandTotal, subtotal)
Im MS Beispiel, Änderung des Parameters Wert Ihrer innerhalb der Task Körper ist eine schlechte Praxis und unnötig. dh Der Code subtotal += nums[j]; return subtotal;
wäre besser, als nur return subtotal + nums[j];
die mit der Lambda-Stenografie Projektion abgekürzt werden könnte (j, loop, subtotal) => subtotal + nums[j]
Allgemeines
Die localInit/body/localFinally
Überlastungen von Parallel.For/Parallel.ForEach erlauben einmal pro Aufgabe Initialisierung und Bereinigungscode zu ausgeführt werden, vor und nach (bzw.) taskBody
Iterationen werden von der Task durchgeführt.
(unter Hinweis auf die für Entfernungs-/Enumerable vorbei zum parallelen For
/Foreach
werden in Chargen von IEnumerable<>
aufgeteilt werden, von denen jeder einer Aufgabe zugeordnet wird)
In jede Aufgabe wird localInit
einmal aufgerufen werden, wiederholt aufgerufen, sobald der body
Code pro Stück in Charge (0..N
mal) wird, und localFinally
wird, sobald nach Abschluss aufgerufen werden.
Darüber hinaus können Sie ein beliebigen Zustand für die Dauer der Aufgabe erforderlich passieren (das heißt zu den taskBody
und localFinally
Delegierten) über einen generischen TLocal
Rückgabewert der localInit Func
- ich habe diese Variable taskLocals
unten genannt.
Gemeinsame Nutzung von „localInit“:
- Erstellen und teure Ressourcen durch den Schleifenkörper benötigt Initialisierung wie eine Datenbankverbindung oder eine Web-Service-Verbindung.
- Keeping Aufgaben Lokale Variablen zu halten (uncontended) laufende Summen oder Sammlungen
- Wenn Sie mehrere Objekte aus
localInit
zum taskBody
und localFinally
zurückkehren müssen, können Sie die Verwendung einer stark typisierte Klasse machen, ein Tuple<,,>
oder, wenn Sie verwenden nur Lambdas für die localInit/taskBody/localFinally
, Sie können auch Daten über eine anonyme Klasse übergeben. Hinweis: Wenn Sie die Rückgabe von localInit
verwenden, um einen Referenztyp für mehrere Aufgaben zu verwenden, müssen Sie die Threadsicherheit für dieses Objekt berücksichtigen - Unveränderlichkeit ist vorzuziehen.
Gemeinsame Nutzung der "localFinally" Aktion:
- Ressourcen freizugeben, wie
IDisposables
in den taskLocals
(zB Datenbankverbindungen, Datei-Handles, Web-Service-Clients, etc.)
- Zusammenfassen/Kombinieren/Reduzieren der von jeder Aufgabe ausgeführten Arbeit zurück in gemeinsame Variablen. Diese geteilten Variablen werden bestritten, so dass die Thread-Sicherheit ein Problem darstellt:
- z.B.
Interlocked.Increment
auf primitive Typen wie ganze Zahlen
lock
oder ähnliches wird für Schreiboperationen
- Nutzen Sie die concurrent collections sparen Zeit und Aufwand erforderlich.
Die taskBody
ist der tight
Teil des Loop-Betrieb - Sie werden diese für die Leistung optimieren möchten.
Dies alles wird am besten mit einem kommentierten Beispiel zusammengefasst:
public void MyParallelizedMethod()
{
// Shared variable. Not thread safe
var itemCount = 0;
Parallel.For(myEnumerable,
// localInit - called once per Task.
() =>
{
// Local `task` variables have no contention
// since each Task can never run by multiple threads concurrently
var sqlConnection = new SqlConnection("connstring...");
sqlConnection.Open();
// This is the `task local` state we wish to carry for the duration of the task
return new
{
Conn = sqlConnection,
RunningTotal = 0
}
},
// Task Body. Invoked once per item in the batch assigned to this task
(item, loopState, taskLocals) =>
{
// ... Do some fancy Sql work here on our task's independent connection
using(var command = taskLocals.Conn.CreateCommand())
using(var reader = command.ExecuteReader(...))
{
if (reader.Read())
{
// No contention for `taskLocal`
taskLocals.RunningTotal += Convert.ToInt32(reader["countOfItems"]);
}
}
// The same type of our `taskLocal` param must be returned from the body
return taskLocals;
},
// LocalFinally called once per Task after body completes
// Also takes the taskLocal
(taskLocals) =>
{
// Any cleanup work on our Task Locals (as you would do in a `finally` scope)
if (taskLocals.Conn != null)
taskLocals.Conn.Dispose();
// Do any reduce/aggregate/synchronisation work.
// NB : There is contention here!
Interlocked.Add(ref itemCount, taskLocals.RunningTotal);
}
Und weitere Beispiele:
Example of per-Task uncontended dictionaries
Example of per-Task database connections
() => Initialisiert nichts, Rückgabewert dieser Funktion wird verwendet, um die lokale Variable (Zwischensumme, in Ihrem Beispiel) zu initialisieren. –