写在前面
《Pro LINQ in C# 2010》第22章开始,讲的是Parallel LINQ,即并行LINQ查询,简称PLINQ。这本是LINQ的一个重大改进和提升,但该书却在这一重大主题上草草结束。作为入门,我更喜欢MSDN上的一篇文章:在多核处理器上运行查询。
PLINQ概要
PLINQ属于LINQ to Objects,将IEnumerable<T>类型的序列用AsParallel()运算符转换为ParallelQuery<T>,即可自动激活并行LINQ查询。
PLINQ的核心在于分页——将待查询的序列按CPU核心数量分为若干页,交给各CPU进行运算。
若没有定序运算符AsOrdered(),则PLINQ返回的查询结果是乱序的、不可靠的(Unstable),多数情况下是按分页顺序取出查询结果。但使用AsOrdered()也是有代价的,因为要重新排序,因此可能抵消使用PLINQ的收益。
多数情况下,PLINQ会自主决定是否采用并行计算。使用WithExecutionMode(ParallelExecutionMode.ForceParallelism)方法,可以强制使用并行查询。
使用WithDegreeOfParallelism(),可以限定并行的分页数,简单地可理解为交给几个CPU核心计算。
PLINQ中的异常处理
区别于通常的串行查询在遇到异常后停止查询,PLINQ中的每个分支是独立的,某个分支发生异常,只会导致该分支的查询被中断。
PLINQ会将每个并行分支中发生的异常收集并封装在异常System.AggregateException里,并在PLINQ查询得到实际执行时被抛出。因此我们要做的,就是利用try-catch捕获异常AggregateException。而在AggregateException内部,提供有一个方法Handle()用以依次处理从各分支中搜集到的异常。该方法的参数为一委托,可以用lambda表达式实现。该委托的返回类型为bool:如果异常被我们处理了,则返回true;如果异常靠我们自己处理不了,就返回false。就象作者在示例中的如下处理方式:
IEnumerable<string> results = presidents .AsParallel() .Select(p => { if (p == "Arthur" || p == "Harding") throw new Exception(String.Format("Problem with President {0}", p)); return p; }); try { foreach (string president in results) { Console.WriteLine("Result: {0}", president); } } catch (AggregateException agex) { agex.Handle(ex => { Console.WriteLine(ex.Message); return true; }); }
通过在select选择子中的lambda表达式抛出异常,然后在try-catch块中捕获,再利用捕获到的AggregateException方法Handle()依次取出其中搜集到的异常。Handle()方法的参数有两个,一个是搜集到的异常,一个是Func<>(可以用Lambda表达式实现)。
PLINQ的其他操作
Range() Repeat() Empty() | 与LINQ to Object的类似。 |
AsOrdered() | 按源序列中的先后顺序返回查询结果 |
AsUnordered() | 相当于Undo AsOrdered() |
AsParallel() | 转换成ParallelQuery<T> |
AsSequential() AsEnumerable() | 作用与AsParallel()相反 |
WithDegreeOfParallelism(int degree) | 设置分页数 |
WithExecutionMode(ParallelExecutionMode) | 是否强制启动并行查询 Default:由PLINQ决定是否启动并行查询 ForceParallelism:强调启动并行查询 |
WithMergeOptions(ParallelMergeOptions) | 返回查询结果的时机 NotBuffered:产生结果即刻返回 FullyBuffered:待全部结果产生后才返回 AutoBuffered/Default:由PLINQ决定Buff大小,并在Buff填满时返回结果 |
Cast() OfType() | 类似LINQ to Objects中的操作 |
ForAll() | 类似泛型的ForEach() |
(全书完)