29.2 Parallel类
Parallel类是继Task类后的另一个重要类型,它位于System.Threading.Tasks命名空间中,主要功能如下:
❑提供并行循环支持,形式上类似于for、foreach循环,但却有本质的不同。
❑让一组操作以并行的方式执行,但并不能保证操作一定是并行执行的。另外,该组操作的执行顺序也不能控制。
Parallel是一个静态类,它只包含方法成员,并且所有成员也都是静态的,其中绝大部分静态方法都是并行迭代(For和ForEach)的多个重载版本,每个重载版本对迭代的控制和状态操作有不同的控制能力。例如,可以停止或中断循环执行、监视其他线程上循环的状态、维护线程本地状态、完成线程本地对象、控制并发程度等,这些都可以通过一些具有设置功能的参数实现。例如ParallelOptions可以配置方法的操作选项,更多关于并行操作控制的内容请参考29.6.1节。下面列出的是Parallel类提供的一些主要方法,其中For和ForEach只列举了一个重载作为代表,更多信息请参考MSDN文档。
❑public static ParallelLoopResult For(int fromInclusive,int toExclusive,Action<int>body);
此重载可以并行执行for循环,可能会并行运行迭代,但不一定。其中,fromInclusive是开始索引,toExclusive是结束索引,body则是一个泛型委托,每次迭代都会执行该委托。该委托的签名如下所示:
public delegate void Action<T>(T obj);
❑public static ParallelLoopResult ForEach<TSource>(IEnumerable<TSource>source,Action<TSource>body);
可以对IEnumerable<TSource>类型执行foreach迭代操作,可能会并行运行迭代。类型参数TSource是集合中的元素类型,source是要迭代的集合,body是Action<T>委托类型(其签名如前面所示),每次迭代都会执行此委托。
❑public static void Invoke(params Action[]actions);
尽可能并行执行每个操作,但并不能保证一定并行执行,操作的执行顺序也不能控制。其中actions是要执行的Action数组,Action是委托类型,其签名如下所示。
public delegate void Action();
❑public static void Invoke(ParallelOptions parallelOptions,params Action[]actions);
尽可能并行执行每个操作,但用户可以对操作进行更多的控制,例如取消操作。这种控制是通过parallelOptions实现的。actions参数同上,是要执行的Action数组。
从For和ForEach方法的签名可以发现一个共同之处,那就是它们的返回值都是ParallelLoopResult,它是一个结构类型,如代码清单29-2所示。
代码清单29-2 ParallelLoopResult结构源代码
public struct ParallelLoopResult
{
internal bool m_completed;
internal long?m_lowestBreakIteration;
public bool IsCompleted
{
get
{
return this.m_completed;
}
}
public long?LowestBreakIteration
{
get
{
return this.m_lowestBreakIteration;
}
}
}
此结构类型的主要功能是提供Parallel循环的完成状态,它只有两个属性:IsCompleted和LowestBreakIteration,这两个属性的说明如表29-2所示。
如果IsCompleted返回true,则循环运行已完成;如果IsCompleted返回false,并且LowestBreakIteration返回非null,表示使用Stop提前结束循环;如果IsCompleted返回false,并且LowestBreakIteration返回非null整数值,表示使用Break提前结束循环。此外,未捕获的异常也会导致并行循环中止,此时ParallelLoopState的IsExceptional属性为true。
代码清单29-1 中已经演示了For方法的基本用法,接下来将重点介绍一些比较有代表性的For方法的重载版本。
29.2.1 For方法重载一
第一个重载:For(Int32,Int32,Action<Int32,ParallelLoopState>),其完整签名如下:
public static ParallelLoopResult For(int fromInclusive,
int toExclusive,
Action<int,ParallelLoopState>body);
通过此重载版本可学到的知识如下:
❑此重载的body参数的类型与之前的不同,这里使用的是具有2个参数的Action<T1,T2>委托类型,其签名如下:
public delegate void Action<T1,T2>(T1 arg1,T2 arg2);
❑在此重载版本中,T1的类型实参是int,T2的类型实参是ParallelLoopState,因此这实际上是一个Action<int,ParallelLoopState>委托,此委托中的第2个参数是ParallelLoopState类型的实例,此实例由Parallel类创建并提供给每个循环,通过ParallelLoopState的实例可以在迭代之间进行交互。
在代码清单29-3中,主要展示了返回类型ParallelLoopResult和Action<int,ParallelLoopState>委托中ParallelLoopState类型实参的用法。
代码清单29-3 For(Int32,Int32,Action<Int32,ParallelLoopState>)重载示例代码
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ProgrammingCSharp4
{
class ParallelSample
{
static void Main(string[]args)
{
ParallelLoopResult result=Parallel.For(0,100,(i,state)=>
{
Console.WriteLine("[{0}]Thread[{1}]:{2}",
DateTime.Now.ToString("hh:mm:ss fff"),
Thread.CurrentThread.ManagedThreadId,i);
if(i==5)
{
state.Break();
}
});
Console.WriteLine("result.IsCompleted={0}",
result.IsCompleted);
Console.WriteLine("result.LowestBreakIteration={0}",
result.LowestBreakIteration);
}
}
}
为了更详细地说明问题,我们特别将输出时间精确到了毫秒,上述代码的运行结果如下:
[05:58:15 887]Thread[1]:0
[05:58:15 887]Thread[3]:1
[05:58:15 934]Thread[3]:4
[05:58:15 934]Thread[1]:2
[05:58:15 934]Thread[1]:3
[05:58:15 887]Thread[4]:50
result.IsCompleted=False
result.LowestBreakIteration=4
请按任意键继续……
我们在示例代码中设定,当i值等于5时,调用state参数的Break方法。但是从结果来看,循环却执行了6次,这并不奇怪,因为Break方法将通知For操作当前迭代后的迭代不需要执行。其中当Break方法调用时迭代停止的时间为:
[05:58:15 934]Thread[3]:4
仔细观察,线程4是在此时间之前执行的,并不是在Break调用后,因此它也被输出了:
[05:58:15 887]Thread[4]:50