Gewusst wie: Beenden oder Verlassen einer Parallel.For-Schleife
Im folgenden Beispiel wird das Verlassen (oder Beenden in Visual Basic) einer For-Schleife sowie das Anhalten einer Schleife dargestellt. In diesem Zusammenhang bezeichnet "Verlassen" den Abschluss aller Iterationen in allen Threads die vor der aktuellen Iteration im aktuellen Thread erfolgen, und das anschließende Beenden der Schleife. " "Beenden" bedeutet, dass alle Iterationen sobald wie möglich gestoppt werden.
Beispiel
In diesem Beispiel wird eine For-Schleife verwendet. Auf dieselbe Weise können Sie aber auch eine ForEach-Schleife beenden oder abbrechen. In einer ForEach-Schleife wird für jedes Element in jeder Partition intern ein Iterationsindex generiert.
' How to: Stop or Break from a Parallel.For Loop
Imports System.Collections.Concurrent
Imports System.Threading
Imports System.Threading.Tasks
Module ParallelForStop
Sub Main()
StopLoop()
BreakAtThreshold()
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
Sub StopLoop()
Console.WriteLine("Stop loop...")
Dim source As Double() = MakeDemoSource(1000, 1)
Dim results As New ConcurrentStack(Of Double)()
' i is the iteration variable. loopState is a
' compiler-generated ParallelLoopState
Parallel.For(0, source.Length, Sub(i, loopState)
' Take the first 100 values that are retrieved
' from anywhere in the source.
If i < 100 Then
' Accessing shared object on each iteration
' is not efficient. See remarks.
Dim d As Double = Compute(source(i))
results.Push(d)
Else
loopState.[Stop]()
Exit Sub
End If
' Close lambda expression.
End Sub)
' Close Parallel.For
Console.WriteLine("Results contains {0} elements", results.Count())
End Sub
Sub BreakAtThreshold()
Dim source As Double() = MakeDemoSource(10000, 1.0002)
Dim results As New ConcurrentStack(Of Double)()
' Store all values below a specified threshold.
Parallel.For(0, source.Length, Function(i, loopState)
Dim d As Double = Compute(source(i))
results.Push(d)
If d > 0.2 Then
' Might be called more than once!
loopState.Break()
Console.WriteLine("Break called at iteration {0}. d = {1} ", i, d)
Thread.Sleep(1000)
End If
Return d
End Function)
Console.WriteLine("results contains {0} elements", results.Count())
End Sub
Function Compute(ByVal d As Double) As Double
'Make the processor work just a little bit.
Return Math.Sqrt(d)
End Function
' Create a contrived array of monotonically increasing
' values for demonstration purposes.
Function MakeDemoSource(ByVal size As Integer, ByVal valToFind As Double) As Double()
Dim result As Double() = New Double(size - 1) {}
Dim initialval As Double = 0.01
For i As Integer = 0 To size - 1
initialval *= valToFind
result(i) = initialval
Next
Return result
End Function
End Module
namespace StopOrBreak
{
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
class Test
{
static void Main()
{
StopLoop();
BreakAtThreshold();
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
private static void StopLoop()
{
Console.WriteLine("Stop loop...");
double[] source = MakeDemoSource(1000, 1);
ConcurrentStack<double> results = new ConcurrentStack<double>();
// i is the iteration variable. loopState is a
// compiler-generated ParallelLoopState
Parallel.For(0, source.Length, (i, loopState) =>
{
// Take the first 100 values that are retrieved
// from anywhere in the source.
if (i < 100)
{
// Accessing shared object on each iteration
// is not efficient. See remarks.
double d = Compute(source[i]);
results.Push(d);
}
else
{
loopState.Stop();
return;
}
} // Close lambda expression.
); // Close Parallel.For
Console.WriteLine("Results contains {0} elements", results.Count());
}
static void BreakAtThreshold()
{
double[] source = MakeDemoSource(10000, 1.0002);
ConcurrentStack<double> results = new ConcurrentStack<double>();
// Store all values below a specified threshold.
Parallel.For(0, source.Length, (i, loopState) =>
{
double d = Compute(source[i]);
results.Push(d);
if (d > .2)
{
// Might be called more than once!
loopState.Break();
Console.WriteLine("Break called at iteration {0}. d = {1} ", i, d);
Thread.Sleep(1000);
}
});
Console.WriteLine("results contains {0} elements", results.Count());
}
static double Compute(double d)
{
//Make the processor work just a little bit.
return Math.Sqrt(d);
}
// Create a contrived array of monotonically increasing
// values for demonstration purposes.
static double[] MakeDemoSource(int size, double valToFind)
{
double[] result = new double[size];
double initialval = .01;
for (int i = 0; i < size; i++)
{
initialval *= valToFind;
result[i] = initialval;
}
return result;
}
}
}
In einer ParallelFor()-Schleife oder [Overload:System.Threading.Tasks.Parallel.Parallel.ForEach`1]-Schleife können Sie nicht die gleiche Break- oder Exit-Anweisung verwenden, die in einer sequenziellen Schleife verwendet wird, da diese Sprachkonstrukte nur für Schleifen gültig sind, eine parallele "Schleife" jedoch eigentlich eine Methode und keine Schleife ist. Verwenden Sie stattdessen die Stop-Methode oder die Break-Methode. Einige der Überladungen von Parallel.For lassen Action<int, ParallelLoopState> (Action(Of Integer, ParallelLoopState) in Visual Basic) als Eingabeparameter zu. Das ParallelLoopState-Objekt wird im Hintergrund von der Laufzeit erstellt, und Sie können diesem im Lambda-Ausdruck einen beliebigen Namen zuweisen.
Im folgenden Beispiel erfordert die Methode nur 100 Werte aus der Quellsequenz. Dabei ist es irrelevant, welche Elemente abgerufen wurden. In diesem Fall wird die Stop-Methode verwendet, da durch diese alle Iterationen der Schleife so schnell wie möglich beendet werden, einschließlich den vor der aktuellen Iteration in anderen Threads gestarteten Iterationen.
In der zweiten Methode werden alle Elemente bis zu einem angegebenen Index in der Quellsequenz abgerufen. In diesem Fall wird Break aufgerufen, da beim Erreichen des Index in einem Thread möglicherweise noch vorhergehende Elemente in der Quelle vorhanden ist, die bislang nicht verarbeitet wurden. Beim Verlassen einer Schleife unterbrechen spätere Threads die Verarbeitung nachfolgender Segmente (falls zutreffend) und beenden die Verarbeitung der vorhergehenden Elemente, bevor die Schleife beendet wird.
Beachten Sie, dass nach einem Aufruf von Stop oder Break andere Threads in einer Schleife weiterhin für einen gewissen Zeitraum ausgeführt werden können, der vom Anwendungsentwickler nicht beeinflusst werden kann. Mithilfe der ParallelLoopState.IsStopped-Eigenschaft können Sie überprüfen, ob die Schleife in einem anderen Thread beendet wurde. Wenn im folgenden Beispiel IsStopped zutrifft (true), werden keine weiteren Daten in die Auflistung geschrieben.
Kompilieren des Codes
- Kopieren Sie das Codebeispiel, und fügen Sie es in ein Visual Studio 2010-Projekt ein.
Siehe auch
Referenz
Konzepte
Datenparallelität (Task Parallel Library)