本文参考书籍《CLR via C#》
一些常见的编程情形呢都可以通过任务来提升性能,为了简化编程,静态System.Threading.Tasks.Parallel封装了这些静态方法,其实它的内部仍然使用了Task对象,只是一些便捷编程的语法糖,但是十分好用。
比如常见的循环:
for(Int32 i=0;i<1000;i++) DoSometing();
就可以用Parallel的for方法:
Parallel.For(0, 10000, i => DoSometing(i));
类似的有一个集合:
foreach(var item in collection) DoSometing(item)
就可用Palallel的ForEach:
Palallel.ForEach(collection,item=>DoSometing(item))
如果既可以用For,也可以用ForEach,那么建议使用For,因为它执行的更快,
如果要执行多个方法:
Method1();
Method2();
Method3();
就可以并行执行:
Palallel.Invole(
()=>Method1(),
()=>Method2(),
()=>Mathod3()
);
比如我们有一个求和的方法:
private static Int32 NumValue(Int32 n) { Int32 sum = 0; for (int i = 0; i < n; i++) { sum += i; } return sum; }
如果使用Parallel.For()就可以写成
private static Int32 ParallelNumValue(Int32 n) { Int32 sum=0; Parallel.For(1, n, i => { sum += i; }); return sum; }
我们求累加到5000000,看一下串行求和和并行求和耗时的差别:
static void Main(string[] args) { Console.WriteLine("============耗时比较============="); { System.Diagnostics.Stopwatch sw=new System.Diagnostics.Stopwatch(); sw.Start(); Int32 a = NumValue(5000000); sw.Stop(); Console.WriteLine("串行求和得{0},耗时{1}ms",a, sw.ElapsedMilliseconds); sw.Restart(); sw.Stop(); Int32 b = NumValue(5000000); Console.WriteLine("并行求和得{0},耗时{1}ms",b,sw.ElapsedMilliseconds); } Console.WriteLine("================================="); Console.ReadLine(); }
测试结果如下:
可见串行要更耗时些。
Parallel的所有方法都让调用线程参与处理。从资源利用的角度说,这是一件好事,因为我们不望调用线程停下来(阻塞),等线程池线程做完所有工作オ能继续。然而,如果调用线程在线程池线程完成自己的那一部分工作之前完成工作,调用线程会将自己挂起,直到所有工作完成。这也是一件好事,因为这提供了和使用普通for或 foreach循环时相同的语义:线程要在所有工作完成后才继续运行。还要注意,如果任何操作抛出未处理的异常,你调用的Parallel方法最后会抛出一个AggregateException。但这不是说需要对自己的源代码进行全文替换,将for循环替换成对 Parallel.For的调用,将forech循环替换成对 Parallel. Foreach的调用。调用 Parallel的方法时有一个很重要的前提条件:工作项必须能并行执行!所以,如果工作必须顺序执行,就不要使用 Paralel的方法。另外,要避免会修改任何共享数据的工作项,否则多个线程同时处理可能会损坏数据。解決这个问题一般的办法是围绕数据访问添加线程同步锁。但这样一次就只能有一个线程访问数据,无法享受并行处理多个项所带来的好处。
另外, Parallel的方法本身也有开销:委托对象必须分配,而针对每个工作项都要调用一次这些委托。如果有大量可由多个线程处理的工作项,那么也许能获得性能的提升。另外,如果每一项都涉及大量工作,那么通过委托来调用所产生的性能损失是可以忽略不计的。但如果只为区区几个工作项使用 Parallel的方法,或者为处理得非常快的工作项使用Parallel的方法,就会得不偿失,反而降低性能。