Практическое руководство. Остановка цикла Parallel.For или выход из этого цикла
В следующем примере показано, как использовать break (или Exit в Visual Basic), чтобы разорвать цикл For, а также как остановить выполнение цикла. В этом контексте "разорвать"означает завершение всех итераций во всех потоках до текущей итерации в текущем потоке с последующим выходом из цикла. " Остановить" означает остановку всех итераций как можно раньше.
Пример
В примере показана работа цикла For; однако остановить или прервать цикл ForEach можно таким же образом. В цикле ForEach индекс итерации создается внутренне для каждого элемента и каждого раздела.
' 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;
}
}
}
В цикле ParallelFor() или [Overload:System.Threading.Tasks.Parallel.Parallel.ForEach`1] нельзя использовать тот же оператор break или Exit, который используется в последовательном цикле, поскольку эти языковые конструкции языка допустимы для циклов, а параллельный "цикл" фактически является методом, а не циклом. Вместо этого используйте метод Stop или метод Break. Некоторые перегрузки Parallel.For принимают Action<int, ParallelLoopState> (Action(Of Integer, ParallelLoopState) в Visual Basic) в качестве входного параметра. Объект ParallelLoopState создается в фоновом режиме средой выполнения, и ему можно присвоить любое имя в лямбда-выражении.
В следующем примере метод требует только 100 значений из исходной последовательности, и неважно, какие элементы были извлечены. В данном случае используется метод Stop, поскольку он указывает как можно раньше остановить выполнение всех итераций цикла, включая начатые до текущей итерации в других потоках.
Во втором методе извлекаются все элементы до указанного индекса в исходной последовательности. В этом случае вызывается метод Break, поскольку по достижении индекса в одном потоке может получиться так, что предыдущие элементы в источнике еще не были обработаны. Разрыв приведет к тому, что другие потоки прекратят выполнение последующих работ (при их наличии) и завершат обработку всех предыдущих элементов до выхода из цикла.
Важно понимать, что после вызова метода Stop или метода Break выполнение других потоков в цикле может продолжиться в течение некоторого периода времени, что не может контролировать разработчик приложения. Свойство ParallelLoopState.IsStopped можно использовать для проверки того, был ли остановлен цикл в другом потоке. В следующем примере, если свойство IsStopped имеет значение true, дальнейшие сведения не записываются в коллекцию.
Компиляция кода
- Скопируйте и вставьте пример кода в проект Visual Studio 2010.
См. также
Ссылки
Основные понятия
Параллелизм данных (библиотека параллельных задач)