Parallel.For 你可能忽视的一个非常实用的重载方法

  说起Parallel.For大家都不会陌生,很简单,不就是一个提供并行功能的for循环吗? 或许大家平时使用到的差不多就是其中最简单的那个重载方法,而真实情况

下Parallel.For里面有14个重载,而其中那些比较复杂的重载方法,或许还有同学还不知道怎么用呢~~~ 刚好我最近我有应用场景了,给大家介绍介绍,废话不多说,

先给大家看一下这个并行方法的重载一览表吧。。。

一:遇到的场景

我遇到的场景是这样的,项目中有这样一个功能,这个功能需要根据多个维度对一组customerIDList进行筛选,最后求得多个维度所筛选出客户的并集,我举个

例子:现有8个维度:

1. 交易行为

2.营销活动

3.地区

4.新老客户

5.营销渠道

6.客户属性

7.客户分组

8.商品

每个维度都能筛选出一批customerid出来,然后对8组customerid求并集,这种场景很明显要提升性能的话,你必须要做并行处理,当然能够实现的方式有很多种,

比如我定义8个task<T>,然后使用WaitAll等待一下,最后再累计每个Result的结果就可以了,代码如下:

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         List<string> rankList = Enum.GetNames(typeof(FilterType)).ToList();
 6
 7         Task<HashSet<int>>[] tasks = new Task<HashSet<int>>[rankList.Count];
 8
 9         var hashCustomerIDList = new HashSet<int>();  //求customerid的并集
10
11         for (int i = 0; i < tasks.Length; i++)
12         {
13             tasks[i] = Task.Factory.StartNew<HashSet<int>>((obj) =>
14             {
15                 //业务方法,耗损性能中。。。
16                 var smallCustomerIDHash = GetXXXMethod(rankList[(int)obj]);
17
18                 return smallCustomerIDHash;
19             }, i);
20         }
21
22         Task.WaitAll(tasks);
23
24         foreach (var task in tasks)
25         {
26             foreach (var item in task.Result)
27             {
28                 hashCustomerIDList.Add(item);
29             }
30         }
31     }
32
33     static HashSet<int> GetXXXMethod(string rank)
34     {
35         return new HashSet<int>();
36     }
37
38     public enum FilterType
39     {
40         交易行为 = 1,
41         营销活动 = 2,
42         地区 = 4,
43         新老客户 = 8,
44         营销渠道 = 16,
45         客户属性 = 32,
46         客户分组 = 64,
47         商品 = 128
48     }
49 }

上面的代码的逻辑还是很简单的,我使用的是Task<T>的模式,当然你也可以用void形式的Task,然后在里面lock代码的时候对hashCustomerIDList进行

插入,实现起来也是非常简单的,我就不演示了,那下面的问题来了,有没有更爽更直接的方式,看人家看上去更有档次一点的方法,而且还要达到这种效果呢?

二:Parallel.For复杂重载

回到文章开头的话题,首先我们仔细分析一下下面这个复杂的重载方法。

 1  //
 2         // 摘要:
 3         //     执行具有线程本地数据的 for(在 Visual Basic 中为 For)循环,其中可能会并行运行迭代,而且可以监视和操作循环的状态。
 4         //
 5         // 参数:
 6         //   fromInclusive:
 7         //     开始索引(含)。
 8         //
 9         //   toExclusive:
10         //     结束索引(不含)。
11         //
12         //   localInit:
13         //     用于返回每个任务的本地数据的初始状态的函数委托。
14         //
15         //   body:
16         //     将为每个迭代调用一次的委托。
17         //
18         //   localFinally:
19         //     用于对每个任务的本地状态执行一个最终操作的委托。
20         //
21         // 类型参数:
22         //   TLocal:
23         //     线程本地数据的类型。
24         //
25         // 返回结果:
26         //     包含有关已完成的循环部分的信息的结构。
27         //
28         // 异常:
29         //   T:System.ArgumentNullException:
30         //     body 参数为 null。- 或 -localInit 参数为 null。- 或 -localFinally 参数为 null。
31         //
32         //   T:System.AggregateException:
33         //     包含在所有线程上引发的全部单个异常的异常。
34         public static ParallelLoopResult For<TLocal>(int fromInclusive, int toExclusive, Func<TLocal> localInit, Func<int, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally);

从上面的代码区域中看,你可以看到上面提供了5个参数,而最后意思的就是后面三个,如果你对linq的扩展方法比较熟悉的话,你会发现这个其实就是一个并行版本

的累加器(Aggregate)操作,因为他们都是具有三个区域:第一个区域就是初始化区域(localInit),就是累积之前的一个初始化操作,第二个区域其实就是一个迭代

区域,说白了就是foreach/for循环,for循环之中,会把计算结果累计到当初初始化区域设置的变量中,第三个区域就是foreach/for之后的一个最终计算区,三者合起

来就是一个并行累加器,为了方便大家更好的理解,我就扒一下源码给大家看看:

由于图太大,就截两张图了,大家一定要仔细体会一下这里面的tlocal变量,因为这个tlocal的使用贯穿着三个区域,所以大家一定要好好体会下面这几句代码

1 TLocal tLocal = default(TLocal);
2
3 tLocal = localInit();
4
5 while(xxx<xxx){
6 tLocal = bodyWithLocal(num5, parallelLoopState, tLocal);
7 }
8 localFinally(tLocal);

当你理解了tLocal具有累积foreach中的item结果之后,你就应该很明白下面这个body=>(item, loop, total) 和 finally => (total) 中total的含义了,

对吧,当你明白了,然后大家可以看看下面这段代码,是不是用一个方法就搞定了原来需要分阶段实现的一个业务逻辑呢?

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         List<string> rankList = Enum.GetNames(typeof(FilterType)).ToList();
 6
 7         var hashCustomerIDList = new HashSet<int>();  //求customerid的并集
 8
 9         //并行计算 7个 维度的 总和
10         Parallel.For(0, rankList.Count, () => { return new List<int>(); }, (item, loop, total) =>
11         {
12             //业务方法,耗损性能中。。。
13             var smallCustomerIDHash = GetXXXMethod(rankList[item]);
14
15             total.AddRange(smallCustomerIDHash);
16
17             return total;
18         }, (total) =>
19         {
20             lock (hashCustomerIDList)
21             {
22                 foreach (var customerID in total)
23                 {
24                     hashCustomerIDList.Add(customerID);
25                 }
26             }
27         });
28     }
29
30     static HashSet<int> GetXXXMethod(string rank)
31     {
32         return new HashSet<int>();
33     }
34
35     public enum FilterType
36     {
37         交易行为 = 1,
38         营销活动 = 2,
39         地区 = 4,
40         新老客户 = 8,
41         营销渠道 = 16,
42         客户属性 = 32,
43         客户分组 = 64,
44         商品 = 128
45     }
46 }

好了,本篇就先说这么多,希望这个具有并行累加器效果的Parallel.For能够给你带来一丝灵感~~~

时间: 2024-08-29 19:09:57

Parallel.For 你可能忽视的一个非常实用的重载方法的相关文章

一个很实用的前端框架Zui

杰哥给我推荐了一个很有用的前端框架-Zui,我看着觉得很神奇的,因为有很多我都不懂.在这里分享总结一下.首先,这是一个中国自己开发的框架,比起很多外国的框架来说,有很详细的API,而且是全中文的,不需要再经过其他人的翻译了.然后,它的内容十分丰富,很系统的分为了:基础,控件,组件,JS插件,视图几大块:而且使用起来,只需要导入js,在适当的地方加上正确的class类就可以了.对于,没有什么js基础的人,也是十分容易上手的.下面我就大体的介绍一下它的各个模块的功能.基础:基础里面我觉得很有用的主要

代码添加一个按钮及监听方法

有时候无法从控件中拖拽一个按钮到storyboard,必须用编写代码方式添加按钮: 1 - (void)viewDidLoad 2 { 3 [super viewDidLoad]; 4 // Do any additional setup after loading the view, typically from a nib. 5 //计算出展示表情区域的宽和 展示区距顶部的高度+10个偏移量 6 //添加按钮 9 //创建button 10 addBtn = [[UIButton alloc

封装一个类似jquery的ajax方法

//封装一个类似jquery的ajax方法,当传入参数,就可以发送ajax请求 //参数格式如下{ // type:"get"/"post", // dataType:"json"/"jsonp", // url:"地址", // data:{key:value} // success:function(){ // } // } //还需要一个跨域方法,可以访问远程服务器的数据 function myAja

程序只启动一个实例的几种方法

我们在使用<金山词霸>时发现,在<金山词霸>已经运行了的情况下,再次点击<金山词霸>的图标,那么它不会再运行另外一个<金山词霸>,而是将已有的<金山词霸>给激活,始终只能运行一个<金山词霸>的实例. 在我们的程序当中如果要实现类似<金山词霸>的功能,就要解决两个问题,首先是要判断该程序已有一个实例在运行,其次是要将已运行的应用程序实例激活,同时退出第二个应用程序实例.  对于第一个问题,我们可以通过设置命名互斥对象或命名信

SpringMVC实现一个controller写多个方法

MultiActionController与ParameterMethodNameResolver在一个Controller类中定义多个方法,并根据使用者的请求来执行当中的某个方法,相当于Struts的DispatcherAction.使用MultiActionController需要配个MethodNameResolver实例,默认使用ParameterMethodNameResolver,他会根据所给的网址中,最后请求的文件名称来判断执行Controller中的哪一个方法 1.控制器类继承M

【转】delphi程序只允许运行一个实例的三种方法:

一.        创建互斥对象 在工程project1.dpr中创建互斥对象 Program project1 Uses Windows,Form, FrmMain in 'FrmMain.pas' {MainForm}; {$R *.res} var hAppMutex: THandle; //声明互斥变量 begin hAppMutex := CreateMutex(nil, false,’projectname’); //创建互斥对象projectname工程名称 if ( (hAppM

分享一个二进制转字符串的方法

1 public string ByteToString(byte[] inputBytes) 2 { 3 StringBuilder temp = new StringBuilder(2048); 4 foreach (byte tempByte in inputBytes) 5 { 6 temp.Append(tempByte > 15 ? 7 Convert.ToString(tempByte, 2) : '0' + Convert.ToString(tempByte, 2)); 8 }

一个Java复制目录的方法(递归)

1 /** 2 * 将目标目录复制为指定目录(也可以用于复制文件) 3 * @param src 源路径 4 * @param dest 目标路径 5 * @throws IOException 6 */ 7 public static void copyDir(File src, File dest) throws IOException { 8 if(!src.exists()) { // 检查源路径是否存在 9 System.out.println("源目录不存在!"); 10

开发一个简单实用的android紧急求助软件

之前女朋友一个人住,不怎么放心,想找一个紧急求助的软件,万一有什么突发情况,可以立即知道.用金山手机卫士的手机定位功能可以知道对方的位置状态,但不能主动发送求助信息,在网上了很多的APK,都是鸡肋功能,都需要解锁.并打开软件,真正的紧急情况可能没有时间来完成这一系列操作. 于是我自己做了一个这样的软件,在紧急情况下,连续按电源键5次即可发送求救短信和位置信息给事先指定的用户,这个操作在裤兜里就能完成.原理很简单,就是设置监听器捕获屏幕的开关,在较短的时间内屏幕开关达到一定次数后,触发手机定位,定