1、基础知识html
并行编程:并行编程是指软件开发的代码,它能在同一时间执行多个计算任务,提升执行效率和性能一种编程方式,属于多线程编程范畴。因此咱们在设计过程当中通常会将不少任务划分红若干个互相独立子任务,这些任务不考虑互相的依赖和顺序。这样咱们就可使用很好的使用并行编程。可是咱们都知道多核处理器的并行设计使用共享内存,若是没有考虑并发问题,就会有不少异常和达不到咱们预期的效果。不过还好NET Framework4.0引入了Task Parallel Library(TPL)实现了基于任务设计而不用处理重复复杂的线程的并行开发框架。它支持数据并行,任务并行与流水线。核心主要是Task,可是通常简单的并行咱们能够利用Parallel提供的静态类以下三个方法。
编程
Parallel.Invoke 对给定任务实现并行开发多线程
Parallel.For 对固定数目的任务提供循环迭代并行开发并发
parallel.Foreach 对固定数目的任务提供循环迭代并行开发框架
注意:全部的并行开发不是简单的觉得只要将For或者Foreach换成Parallel.For与Parallel.Foreach这样简单。异步
PS:从简单的Invoke开始逐步深刻探讨并行开发的主要知识点,也对本身学习过程当中的积累作个总结,其中参考了博客园中的其余优秀博文异步编程
滴答的雨 异步编程:轻量级线程同步基元对象post
首先感谢您,在我学习并行开发过程当中,您的博文对我帮助很大。性能
2、Parallel.Invoke在并行中的使用学习
首先咱们来看看它的两个重载方法:
public static void Invoke(params Action[] actions); public static void Invoke(ParallelOptions parallelOptions, params Action[] actions);
Invoke主要接受params的委托actions,好比咱们要同时执行三个任务,咱们能够这样利用
方式一
Parallel.Invoke(() => Task1(), () => Task2(), () => Task3());
方式二
Parallel.Invoke(Task1, Task2, Task3);
方式三
Parallel.Invoke(
() =>
{
Task1();
},
Task2,
delegate () { Task3(); console.write('do someting!');});
这样Invoke就简单实现了Task1,Task2,Task3的并行开发。下面咱们用实例来讲明他们的执行规则。以及两个重载方法的使用。
三 、Demo
一、 Demo 1:
public class ParallelInvoke { /// <summary> /// Invoke方式一 action /// </summary> public void Client1() { Stopwatch stopWatch = new Stopwatch(); Console.WriteLine("主线程:{0}线程ID : {1};开始", "Client1", Thread.CurrentThread.ManagedThreadId); stopWatch.Start(); Parallel.Invoke(() => Task1("task1"), () => Task2("task2"), () => Task3("task3")); stopWatch.Stop(); Console.WriteLine("主线程:{0}线程ID : {1};结束,共用时{2}ms", "Client1", Thread.CurrentThread.ManagedThreadId, stopWatch.ElapsedMilliseconds); } private void Task1(string data) { Thread.Sleep(5000); Console.WriteLine("任务名:{0}线程ID : {1}", data, Thread.CurrentThread.ManagedThreadId); } private void Task2(string data) { Console.WriteLine("任务名:{0}线程ID : {1}", data, Thread.CurrentThread.ManagedThreadId); } private void Task3(string data) { Console.WriteLine("任务名:{0}线程ID : {1}", data, Thread.CurrentThread.ManagedThreadId); } }
执行运行后结果:
咱们看到Invoke 执行Task三个方法主要有如下几个特色:
一、没有固定的顺序,每一个Task多是不一样的线程去执行,也多是相同的;
二、主线程必须等Invoke中的全部方法执行完成后返回才继续向下执行;这样对咱们之后设计并行的时候,要考虑每一个Task任务尽量差很少,若是相差很大,好比一个时间很是长,其余都比较短,这样一个线程可能会影响整个任务的性能。这点很是重要
三、这个很是简单就实现了并行,不用咱们考虑线程问题。主要Framework已经为咱们控制好线程池的问题。
ps:若是其中有一个异常怎么办? 带作这个问题修改了增长了一个Task4.
二、 Demo2
public class ParallelInvoke { /// <summary> /// Invoke方式一 action /// </summary> public void Client1() { Stopwatch stopWatch = new Stopwatch(); Console.WriteLine("主线程:{0}线程ID : {1};开始", "Client1", Thread.CurrentThread.ManagedThreadId); stopWatch.Start(); try { Parallel.Invoke(() => Task1("task1"), () => Task2("task2"), () => Task3("task3"), delegate () { throw new Exception("我这里发送了异常"); }); } catch (AggregateException ae) { foreach (var ex in ae.InnerExceptions) Console.WriteLine(ex.Message); } stopWatch.Stop(); Console.WriteLine("主线程:{0}线程ID : {1};结束,共用时{2}ms", "Client1", Thread.CurrentThread.ManagedThreadId, stopWatch.ElapsedMilliseconds); } }
主要看 delegate() { throw new Exception("我这里发送了异常");} 增长了这个委托Task3. 而后咱们看结果:
这里咱们发现即便有异常程序也会完成执行,并且不会影响其余Task的执行。
三、demo3 重载方法ParallelOptions 的使用。
理解ParallelOptions建议你们异步编程:轻量级线程同步基元对象 讲的很是详细。
主要理解两个参数:
CancellationToken 控制线程的取消
MaxDegreeOfParallelism 设置最大的线程数,有时候可能会跑遍全部的内核,为了提升其余应用程序的稳定性,就要限制参与的内核
下面从代码上看效果如何?
public class ParallelInvoke { // 定义CancellationTokenSource 控制取消 readonly CancellationTokenSource _cts = new CancellationTokenSource(); /// <summary> /// Invoke方式一 action /// </summary> public void Client1() { Console.WriteLine("主线程:{0}线程ID : {1};开始{2}", "Client3", Thread.CurrentThread.ManagedThreadId, DateTime.Now); var po = new ParallelOptions { CancellationToken = _cts.Token, // 控制线程取消 MaxDegreeOfParallelism = 3 // 设置最大的线程数3,仔细观察线程ID变化 }; Parallel.Invoke(po, () => Task1("task1"), ()=>Task5(po), Task6); Console.WriteLine("主线程:{0}线程ID : {1};结束{2}", "Client3", Thread.CurrentThread.ManagedThreadId, DateTime.Now); } private void Task1(string data) { Thread.Sleep(5000); Console.WriteLine("任务名:{0}线程ID : {1}", data, Thread.CurrentThread.ManagedThreadId); }
// 打印数字 private void Task5(ParallelOptions po) { Console.WriteLine("进入Task5线程ID : {0}", Thread.CurrentThread.ManagedThreadId); int i = 0; while (i < 100) { // 判断是否已经取消 if (po.CancellationToken.IsCancellationRequested) { Console.WriteLine("已经被取消。"); return; } Thread.Sleep(100); Console.Write(i + " "); Interlocked.Increment(ref i); } } /// <summary> /// 10秒后取消 /// </summary> private void Task6() { Console.WriteLine("进入取消任务,Task6线程ID : {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000 * 10); _cts.Cancel(); Console.WriteLine("发起取消请求..........."); } }
执行结果:
从程序结果咱们看到如下特色:
一、程序在执行过程当中线程数码不超过3个。
二、CancellationTokenSource/CancellationToken控制任务的取消。
4、总结
Parallel.Invoke 的使用过程当中咱们要注意如下特色:
一、没有特定的顺序,Invoke中的方法所有执行完才返回,可是即便有异常在执行过程当中也一样会完成,他只是一个很简单的并行处理方法,特色就是简单,不须要咱们考虑线程的问题。
二、若是在设计Invoke中有个须要很长时间,这样会影响整个Invoke的效率和性能,这个咱们在设计每一个task时候必须去考虑的。
三、Invoke 参数是委托方法。
四、固然Invoke在每次调用都有开销的,不必定并行必定比串行好,要根据实际状况,内核环境屡次测试调优才能够。
五、异常处理比较复杂。