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所示。

29.2 Parallel类 - 图1

如果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