.NET 实现并行的几种方式(二)

本随笔续接:.NET 实现并行的几种方式(一)

四、Task

3)Task.NET 4.5 中的简易方式

在上篇随笔中,两个Demo使用的是 .NET 4.0 中的方式,代码写起来略显麻烦,这不 .NET 4.5提供了更加简洁的方式,让我们来看一下吧。

        /// <summary>
        /// Task.NET 4.5 中的简易方式
        /// </summary>
        public void Demo3()
        {
            Task.Run(() =>
            {
                SetTip("简洁的代码");
            });

            Task.Run(() =>
            {
                SetTip("验证 CreationOptions 属性");
            }).ContinueWith((t)=> {
                SetTip("CreationOptions:" + t.CreationOptions.ToString());
            });
        }

Task.NET 4.5 中的简易方式

五、TPL (Task Parallel Library)

TPL (任务并行库)是 .NET 4.0 中的另一个重量级模块,可以极其优雅、便捷地完成并行逻辑的编码工作。

1)Parallel.Invoke并行多个独立的Action

        /// <summary>
        /// Parallel.Invoke并行多个独立的Action
        /// </summary>
        public void Demo1()
        {
            Task.Run(() =>
            {
                List<Action> actions = new List<Action>();

                // 生成并行任务
                for (int i = 0; i < 5; i++)
                {
                    // 注意、这里很关键,不可直接使用i变量。
                    // 原因在稍后的随笔中进行说明
                    int index = i;
                    actions.Add(new Action(() =>
                    {
                        SetTip(string.Format("Task{0} 开始", index));

                        SetTip(string.Format("Task{0} 休眠1秒", index));
                        Thread.Sleep(1000);

                        SetTip(string.Format("Task{0} 休眠5秒", index));
                        Thread.Sleep(5000);

                        SetTip(string.Format("Task{0} 结束", index));
                    }));
                }

                // 执行并行任务
                Parallel.Invoke(actions.ToArray());

                // 当上述的5个任务全部执行完毕后,才会执行该代码
                SetTip("并行任务执行完毕");
            });
        }

Parallel.Invoke并行多个独立的Action

2)Parallel简单的For并行

如果 Parallel.Invoke 看做是任务并行, 则 Parallel.For 则是数据并行,可方便的完成For循环并行遍历。

        /// <summary>
        /// Parallel简单的For并行
        /// </summary>
        public void Demo2()
        {
            // 为了实时更新UI、将代码异步执行
            Task.Run(() =>
            {
                Parallel.For(1, 100, (index) =>
                {
                    SetTip(string.Format("Index:{0}, 开始执行Task", index));

                    Thread.Sleep(1000);
                    SetTip(string.Format("Index:{0}, 开始休眠Action 1秒", index));

                    SetTip(string.Format("Index:{0}, Task执行完毕", index));
                });

                SetTip("并行任务执行完毕");
            });
        }

Parallel简单的For并行

3)Parallel.For并行 并行中的 break、 return、 continue

break : 在 Parallel.For 中使用 ParallelLoopState.Break() 方法代替。

return: 在 Parallel.For 中使用 ParallelLoopState.Break() 方法代替。

continue : 在 Parallel.For 中直接使用 return 即可。

        /// <summary>
        /// 中断Parallel.For并行
        /// </summary>
        public void Demo3()
        {
            // 为了实时更新UI、将代码异步执行
            Task.Run(() =>
            {
                int breakIndex = new Random().Next(10, 50);
                SetTip(" BreakIndex : -------------------------" + breakIndex);

                Parallel.For(1, 100, (index, state) =>
                {
                    SetTip(string.Format("Index:{0}, 开始执行Task", index));

                    if (breakIndex == index)
                    {
                        SetTip(string.Format("Index:{0}, ------------------ Break Task", index));
                        state.Break();
                        // Break方法执行后、
                        // 大于 当前索引的并且未被安排执行的迭代将被放弃
                        // 小于 当前索引的的迭代将继续正常执行直至迭代执行完毕
                        return;
                    }

                    Thread.Sleep(1000);
                    SetTip(string.Format("Index:{0}, 休眠Action 1秒", index));

                    SetTip(string.Format("Index:{0}, Task执行完毕", index));
                });

                SetTip("并行任务执行完毕");
            });
        }

        /// <summary>
        /// 终止Parallel.For并行
        /// </summary>
        public void Demo4()
        {
            // 为了实时更新UI、将代码异步执行
            Task.Run(() =>
            {
                int stopIndex = new Random().Next(10, 50);
                SetTip(" StopIndex : -------------------------" + stopIndex);

                Parallel.For(1, 100, (index, state) =>
                {
                    SetTip(string.Format("Index:{0}, 开始执行Task", index));

                    if (stopIndex == index)
                    {
                        SetTip(string.Format("Index:{0}, ------------------ Stop Task", index));
                        state.Stop();
                        // Stop方法执行后
                        // 整个迭代将被放弃
                        return;
                    }

                    Thread.Sleep(1000);
                    SetTip(string.Format("Index:{0}, 休眠Action 1秒", index));

                    SetTip(string.Format("Index:{0}, Task执行完毕", index));
                });

                SetTip("并行任务执行完毕");
            });
        }

Parallel.For并行 并行中的 break、 return、 continue

4)Parallel.For并行中的数据聚合

在并行中,绝大多数委托都是在不同的线程中运行的,如果需要在并行中进行的数据共享、则需要考虑线程同步问题,然而线程同步会影响并行性能。

为了解决特定情况下的数据共享,而又不会因为线程同步而影响性能,Parallel.For 提供了解决方案:

        /// <summary>
        /// Parallel.For并行中的数据聚合
        /// </summary>
        public void Demo5()
        {
            Task.Run(() =>
            {
                // 求 1 到 10 的阶乘的 和
                long total = 0;
                Parallel.For<long>(1, 10,
                    () =>
                    {
                        SetTip("LocalInit");
                        return 0;
                    },
                    (index, state, local) =>
                    {
                        SetTip("Body");
                        int result = 1;
                        for (int i = 2; i < index; i++)
                        {
                            result *= i;
                        }
                        local += result;
                        return local;
                    },
                    (x) =>
                    {
                        SetTip("LocalFinally");
                        Interlocked.Add(ref total, x);
                    });

                SetTip("Total : " + total);
                SetTip("并行任务执行完毕");
            });

        }

Parallel.For并行中的数据聚合

MSDN备注:
对于参与循环执行的每个线程调用 LocalInit 委托一次,并返回每个线程的初始本地状态。
这些初始状态传递到每个线程上的第一个 body 调用。 然后,每个后续正文调用返回可能修改过的状态值,传递到下一个正文调用。
最后,每个线程上的最后正文调用返回传递给 LocalFinally 委托的状态值。
每个线程调用 localFinally 委托一次,以对每个线程的本地状态执行最终操作。
此委托可以被多个线程同步调用;因此您必须同步对任何共享变量的访问。

也就是说:
1) 并行中开辟的线程数 决定了 LocalInit、LocalFinally 的调用次数
2) 多个 迭代委托、Body 可能被同一个线程调用。
3) 迭代委托、Body 中的 local值,并不一定是 LocalInit 的初始值,也有可能是被修改的返回值。
4) LocalFinally 可能是被同时调用的,需要注意线程同步问题。

5)Parallel.ForEach并行

Parallel.ForEach算是另一种数据并行方式, 它与大家熟知的 IEnumerable<TSource> 接口结合十分紧密,是 foreach的并行版本。

        /// <summary>
        /// Parallel.ForEach并行
        /// </summary>
        public void Demo6()
        {
            Task.Run(() =>
            {
                Parallel.ForEach<int>(Enumerable.Range(1, 10), (num) =>
                {
                    SetTip("Task 开始");

                    SetTip("Task 休眠" + num + "秒");
                    Thread.Sleep(TimeSpan.FromSeconds(num));

                    SetTip("Task 结束");
                });

                SetTip("并行任务执行完毕");
            });
        }

Parallel.ForEach并行

6)Parallel.ForEach中的索引,中断、终止操作

在 Parallel.ForEach 中也可以轻易的获得其遍历的索引

        /// <summary>
        /// Parallel.ForEach中的索引,中断、终止操作
        /// </summary>
        public void Demo7()
        {
            Task.Run(() =>
            {
                Parallel.ForEach<int>(Enumerable.Range(0, 10), (num, state, index) =>
                {
                    // num, 并行数据源中的数据项
                    // state,
                    SetTip(" Index : " + index + "         Num: " + num);
                });
                SetTip("并行任务执行完毕");
            });
        }

Parallel.ForEach中的索引,中断、终止操作

本随笔到此、暂告一段落。

附,Demo : http://files.cnblogs.com/files/08shiyan/ParallelDemo.zip

参见更多:随笔导读:同步与异步

(未完待续...)

时间: 2024-10-09 18:08:44

.NET 实现并行的几种方式(二)的相关文章

.NET 实现并行的几种方式(三)

本随笔续接:.NET 实现并行的几种方式(二) 在前两篇随笔中,先后介绍了 Thread .ThreadPool .IAsyncResult (即 APM系列) .Task .TPL (Task Parallel Library). 写到这些笔者突然意识到 还有一个EMP系列没有写,在这里补充一下: 六. EPM .EPM中的典型代表是 WebClient: EPM系列采用 ***Async方法 + ***Completed事件 的编码规范,不做太多解释.具体的demo如下: var addre

.NET 实现并行的几种方式(一)

好久没有更新了,今天来一篇,算是<同步与异步>系列的开篇吧,加油,坚持下去(PS:越来越懒了). 一.Thread 利用Thread 可以直接创建和控制线程,在我的认知里它是最古老的技术了.因为out了.所以不再写例子了. 二.ThreadPool 由于线程的创建和销毁需要耗费大量的资源,为了提过性能.引入了线程池.即ThreadPool,ThreadPool 可隐式完成线程的创建和分配管理工作. 以下是来自MSDN的几句备注: 线程池根据需要提供新的工作线程或 I/O 完成线程,直到其达到每

Spring学习(二)spring ioc注入的三种方式

一.spring ioc注入有哪三种方式: a setter 原理 : 在目标对象中,定义需要注入的依赖对象对应的属性和setter方法:"让ioc容器调用该setter方法",将ioc容器实例化的依赖对象通过setter注入给目标对象,封装在目标对象的属性中. b 构造器 原理 : 为目标对象提供一个构造方法,在构造方法中添加一个依赖对象对应的参数.ioc容器解析时,实例化目标对象时会自动调用构造方法,ioc只需要为构造器中的参数进行赋值:将ioc实例化的依赖对象作为构造器的参数传入

生成二维码的两种方式

利用qrcode生成二维码,(qrcode矩形二维码符号) 基于jquery的二维码生成插件qrcode,在页面中调用该插件就能生成对应的二维码.qrcode其实是通过使用jQuery实现图形渲染,画图,支持canvas(HTML5)和table两种方式: 使用插件时 1.首先在页面中加入jquery库文件和qrcode插件. <script type="text/javascript" src="jquery.js"></script> &

UserView--第二种方式(避免第一种方式Set饱和),基于Spark算子的java代码实现

UserView--第二种方式(避免第一种方式Set饱和),基于Spark算子的java代码实现 测试数据 java代码 1 package com.hzf.spark.study; 2 3 import java.util.Map; 4 import java.util.Set; 5 6 import org.apache.spark.SparkConf; 7 import org.apache.spark.api.java.JavaPairRDD; 8 import org.apache.s

二维数组的认识及其表示元素的两种方式

/* ============================================================================ Name : TeatArr.c Author : lf Version : Copyright : Your copyright notice Description : 二维数组的认识以及其表示元素的两种方式 备注说明 1 要理解二维数组的存储方式. 2 实际上利用a[i][j]的方式并不"正统",这是这靠近我们的 常识一些

Oracle并行更新的两种方式(merge/update内联视图)

对于Oracle的两表联合更新的场景(有A.B两表,以A.id=B.id关联,根据B表中的记录更新A表中的相应字段),一般有update内联视图和merge两种方式,下面举例介绍:   创建用例表: create table test1(id number(10),name varchar2(20)); create table test2(id number(10),name varchar2(20));   测试数据: begin insert into test1 values(1,'A'

Android——数据存储(四种方式之二)读写SD卡

Android--数据存储(四种方式) 1.SharedPrefereces 只能保存一些简单的数轻量级.XML  存储文件名, 数据保存在data/data/basepackage/shared_prefs/myopt.xml中    实例-收藏-记住密码自动登录 //一种轻量级的数据存储方式//通过KEY 存入数据--putxxxx(key,value) 取出数据--getxxxx(key  default) 2.读写SD卡  SD的根目录  适用于数据流读写 实现步骤:加入读写SD卡权限

PHP生成二维码的2种方式

伸展树模版真的好长好长... cut a b c:把第a-1个数伸展到根节点,把第b+1个数伸展到a的右子树,然后把ch[ch[root][1][0]]拿掉,放在剩下的树的第c个节点下. flip a b:把第a-1个数伸展到根节点,把第b+1个数伸展到a的右子树,然后翻转ch[ch[root][1][0]]: 由于会出现操作两边的情况,所以加了两个-1节点. 注意: 1,输出的时候要注意空格和换行. 2,在拿掉子树的时候要注意push_up(); #include<stdio.h> #inc