在.NET并行循环中当满足条件时中断线程继续的方式与单线程循环不太一样。
在单线程顺序执行过程中,我们常会调用break来中断继续循环运行;而在并行循环中有以下几种情况可以分别完成循环的终止。
1、并行中止
终止循环前,并行中断允许完成当前迭代之前的所有线程上的所有的迭代,而并行停止则立即终止所有的迭代。这是并行中断与并行停止的区别。
并行中止,即调用ParallelLoopState对象中的Break方法来请求中断循环。调用Break方法不会停止已经开始的运行步骤,所有比当前索引值小的迭代也会继续运行。
注意:比调用Break()方法的步骤的索引值还高的那些步骤可能已经运行,但没有办法预测它何时开始运行或者它是否已经开始运行。
在长时间运行的循环体中,可能需要检查中断条件;如果有中断请求,则立即退出该步骤。如果不这么做,就将继续运行直到该步骤运行结束。可通过检索并行循环状态的LowestBreakIteration属性的值知晓循环中是否有中断请求,如果它返回一个可空的长整数,且其HasValue属性值为true,就知道有中断请求,也可以读取循环状态对象的ShouldExitCurrentIteration属性,就行读取其它终止条件一样检测出中断。
static void DoLoopFn()
{
int n = 1000;
var loopResult = Parallel.For(0, n, (i, loopState) =>
{
DoWrok(i);
if (i >= 10)
{
//Console.WriteLine("当前值:{0}满足中断条件。", i);
loopState.Break();
return;
}
});
if (loopResult.IsCompleted)
{
Console.WriteLine("循环体正常运行完毕,过程没有调用Break、Stop方法。");
}
else if (loopResult.LowestBreakIteration.HasValue) //即条件(!loopResult.IsCompleted && loopResult.LowestBreakIteration.HasValue)
{
Console.WriteLine("循环体过程有调用Break方法,中断项索引{0}。", loopResult.LowestBreakIteration.Value);
}
else //即(!loopResult.IsCompleted && !loopResult.LowestBreakIteration.HasValue)
{
Console.WriteLine("循环体通过调用Stop方法而终止。");
}
}
运行上面代码,结果示意图:
2、并行停止
这种情况是当停止条件达到,循环尽可能快的终止。调用对象ParallelLoopState对象中的Stop方法来请求停止循环。
static void DoLoopFn()
{
int n = 1000;
var loopResult = Parallel.For(0, n, (i, loopState) =>
{
DoWrok(i);
if (i >= 10)
{
Console.WriteLine("当前值:{0}满足停止条件。", i);
loopState.Stop();
Console.WriteLine("{0}已调用停止方法。", i);
return;
}
});
if (loopResult.IsCompleted)
{
Console.WriteLine("循环体正常运行完毕,过程没有调用Break、Stop方法。");
}
else if (loopResult.LowestBreakIteration.HasValue) //即条件(!loopResult.IsCompleted && loopResult.LowestBreakIteration.HasValue)
{
Console.WriteLine("循环体过程有调用Break方法,中断项索引{0}。", loopResult.LowestBreakIteration.Value);
}
else //即(!loopResult.IsCompleted && !loopResult.LowestBreakIteration.HasValue)
{
Console.WriteLine("循环体通过调用Stop方法而终止。");
}
}
当Stop方法被调用时,导致停止的那次迭代的索引值是不可用的。
在同一并行循环中,如果同时调用Break方法和Stop方法,将抛出一个异常。
3、外部循环取消
在某些情况下,由于外部请求导致循环被取消。在.NET中用CancellationTokenSource类去表示取消,外部取消需要一个取消标记源对象;用CancellationToken结构去检测和响应一个取消请求,该结构能够找出是否有挂起的请求。
调用CancellationToken对象方法Cancel,会抛出异常OperationCanceledException,该异常实例会成为AggregateException的内部异常。
namespace CancelParallelLoops
{
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main()
{
int[] nums = Enumerable.Range(0, 10000000).ToArray();
CancellationTokenSource cts = new CancellationTokenSource();
// Use ParallelOptions instance to store the CancellationToken
ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
Console.WriteLine("Press any key to start. Press 'c' to cancel.");
Console.ReadKey();
// Run a task so that we can cancel from another thread.
Task.Factory.StartNew(() =>
{
if (Console.ReadKey().KeyChar == 'c')
cts.Cancel();
Console.WriteLine("press any key to exit");
});
try
{
Parallel.ForEach(nums, po, (num) =>
{
double d = Math.Sqrt(num);
Console.WriteLine("{0} on {1}", d, Thread.CurrentThread.ManagedThreadId);
po.CancellationToken.ThrowIfCancellationRequested();
});
}
catch (OperationCanceledException e)
{
Console.WriteLine(e.Message);
}
Console.ReadKey();
}
}
}