C#并行编程中的Parallel.Invoke

一、基础知识

并行编程:并行编程是指软件开发的代码,它能在同一时间执行多个计算任务,提高执行效率和性能一种编程方式,属于多线程编程范畴。所以我们在设计过程中一般会将很多任务划分成若干个互相独立子任务,这些任务不考虑互相的依赖和顺序。这样我们就可以使用很好的使用并行编程。但是我们都知道多核处理器的并行设计使用共享内存,如果没有考虑并发问题,就会有很多异常和达不到我们预期的效果。不过还好NET Framework4.0引入了Task Parallel Library(TPL)实现了基于任务设计而不用处理重复复杂的线程的并行开发框架。它支持数据并行,任务并行与流水线。核心主要是Task,但是一般简单的并行我们可以利用Parallel提供的静态类如下三个方法。

Parallel.Invoke  对给定任务实现并行开发

Parallel.For  对固定数目的任务提供循环迭代并行开发

parallel.Foreach 对固定数目的任务提供循环迭代并行开发

注意:所有的并行开发不是简单的以为只要将For或者Foreach换成Parallel.For与Parallel.Foreach这样简单。

PS:从简单的Invoke开始逐步深入探讨并行开发的主要知识点,也对自己学习过程中的积累做个总结,其中参考了博客园中的其他优秀博文

滴答的雨 异步编程:轻量级线程同步基元对象

首先感谢您,在我学习并行开发过程中,您的博文对我帮助很大。

二、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

1、 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三个方法主要有以下几个特点:

1、没有固定的顺序,每个Task可能是不同的线程去执行,也可能是相同的;

2、主线程必须等Invoke中的所有方法执行完成后返回才继续向下执行;这样对我们以后设计并行的时候,要考虑每个Task任务尽可能差不多,如果相差很大,比如一个时间非常长,其他都比较短,这样一个线程可能会影响整个任务的性能。这点非常重要

3、这个非常简单就实现了并行,不用我们考虑线程问题。主要Framework已经为我们控制好线程池的问题。

ps:如果其中有一个异常怎么办? 带做这个问题修改了增加了一个Task4.

2、 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的执行。

3、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("发起取消请求...........");
        }
}    

执行结果:

从程序结果我们看到以下特点:

1、程序在执行过程中线程数码不超过3个。

2、CancellationTokenSource/CancellationToken控制任务的取消。

四、总结

Parallel.Invoke 的使用过程中我们要注意以下特点:

1、没有特定的顺序,Invoke中的方法全部执行完才返回,但是即使有异常在执行过程中也同样会完成,他只是一个很简单的并行处理方法,特点就是简单,不需要我们考虑线程的问题。

2、如果在设计Invoke中有个需要很长时间,这样会影响整个Invoke的效率和性能,这个我们在设计每个task时候必须去考虑的。

3、Invoke 参数是委托方法。

4、当然Invoke在每次调用都有开销的,不一定并行一定比串行好,要根据实际情况,内核环境多次测试调优才可以。

5、异常处理比较复杂。

时间: 2024-10-06 04:38:28

C#并行编程中的Parallel.Invoke的相关文章

Net并行编程高级教程--Parallel

Net并行编程高级教程--Parallel 一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控>这本书中也多次提到并发,不管是计算机还是生物都并发处理着各种事物.人真是奇怪,当你关注一个事情的时候,你会发现周围的事物中就常出现那个事情.所以好奇心驱使下学习并发.便有了此文. 一.理解硬件线程和软件线程 多核处理器带有一个以上的物理内核--物理内核是真正的独立处理单元,多个物理内核使得多条指令能够

并行编程中的内存回收Hazard Pointer

接上篇使用RCU技术实现读写线程无锁,在没有GC机制的语言中,要实现Lock free的算法,就免不了要自己处理内存回收的问题. Hazard Pointer是另一种处理这个问题的算法,而且相比起来不但简单,功能也很强大.锁无关的数据结构与Hazard指针中讲得很好,Wikipedia Hazard pointer也描述得比较清楚,所以我这里就不讲那么细了. 一个简单的实现可以参考我的github haz_ptr.c 原理 基本原理无非也是读线程对指针进行标识,指针(指向的内存)要释放时都会缓存

【读书笔记】.Net并行编程高级教程--Parallel

一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控>这本书中也多次提到并发,不管是计算机还是生物都并发处理着各种事物.人真是奇怪,当你关注一个事情的时候,你会发现周围的事物中就常出现那个事情.所以好奇心驱使下学习并发.便有了此文. 一.理解硬件线程和软件线程 多核处理器带有一个以上的物理内核--物理内核是真正的独立处理单元,多个物理内核使得多条指令能够同时并行运行.硬件线程也称为逻辑内核,一个物

并行编程中的取消任务、共享状态,等等

在面对相互独立的数据或者相互独立的任务时,也许正是Parallel登场的时候. 比如说有一个盒子的集合,分别让盒子旋转一定的角度. void RotateBox(IEnumerable<Box> boxes, float degree) { Parallel.ForEach(boxes, box => box.Rotate(degree)); } 如果并行任务中的一个任务出现异常,需要结束出问题的任务呢? Parallel.ForEach为我们提供了一个重载方法,可以控制任务是否继续.

分布式异步消息框架构建笔记5——如何避开并行编程中的数据共享陷阱

任何多线程/并行/分布式都会面临一个问题,"数据状态共享". 有经验的开发者会说,要想正确有效的避开避开状态共享,那么就应该别用任何状态共享. 虽然不得不说,这是一个不错的建议,但是没有状态共享,你需要如何才能知道非本地数据的状态? 也许你会说使用消息,使用消息来处理,那么我们丑陋的回调金字塔应该叠的更高了. 不得不说这是一个解决办法,但是为了保持状态不被修改,那么我们还得在远程申请一个写入锁,防止数据被别的任务所修改. 那么流程就是 申请锁->请求某个消息状态->释放锁

Parallel并行编程初步

Parallel并行编程可以让我们使用极致的使用CPU.并行编程与多线程编程不同,多线程编程无论怎样开启线程,也是在同一个CPU上切换时间片.而并行编程则是多CPU核心同时工作.耗时的CPU计算操作选择并行是明智的.通常情况,每个CPU核心代表一个硬件线程,但超线程技术,可以使一个cpu核心具有两个硬件线程.软件线程顾名思义就是我们在程序中所开启的. 下面看一个最基础的并行编程的例子,也足以体现多核心并行运行的好处,当然微软.NET为我们封装后,我们也不必过多关注底层操作,那我们就看一下运行结果

C#并行编程-Parallel

原文:C#并行编程-Parallel 菜鸟学习并行编程,参考<C#并行编程高级教程.PDF>,如有错误,欢迎指正. TPL中引入了一个新命名空间System.Threading.Tasks,在该命名空间下Task是主类,表示一个类的异步的并发的操作,创建并行代码的时候不一定要直接使用Task类,在某些情况下可以直接使用Parallel静态类(System.Threading.Tasks.Parallel)下所提供的方法,而不用底层的Task实例. Parallel.Invoke  试图将很多方

Parallel并行编程

Parallel并行编程 Parallel并行编程可以让我们使用极致的使用CPU.并行编程与多线程编程不同,多线程编程无论怎样开启线程,也是在同一个CPU上切换时间片.而并行编程则是多CPU核心同时工作.耗时的CPU计算操作选择并行是明智的.通常情况,每个CPU核心代表一个硬件线程,但超线程技术,可以使一个cpu核心具有两个硬件线程.软件线程顾名思义就是我们在程序中所开启的. 下面看一个最基础的并行编程的例子,也足以体现多核心并行运行的好处,当然微软.NET为我们封装后,我们也不必过多关注底层操

C#中的多线程 - 并行编程 z

原文:http://www.albahari.com/threading/part5.aspx 专题:C#中的多线程 1并行编程Permalink 在这一部分,我们讨论 Framework 4.0 加入的多线程 API,它们可以充分利用多核处理器. 并行 LINQ(Parallel LINQ)或称为 PLINQ Parallel类 任务并行(task parallelism)构造 SpinLock 和 SpinWait 这些 API 可以统称为 PFX(Parallel Framework,并行