C#并行编程(3):并行循环
初识并行循环
并行循环主要用来处理数据并行的,如,同时对数组或列表中的多个数据执行相同的操作。
在C#编程中,我们使用并行类System.Threading.Tasks.Parallel提供的静态方法Parallel.For和Parallel.ForEach来实现并行循环。从方法名可以看出,这两个方法是对常规循环for和foreach的并行化。
简单用法
使用并行循环时需要传入循环范围(集合)和操作数据的委托Action<T>:
Parallel.For(0, 100, i => { Console.WriteLine(i); });
Parallel.ForEach(Enumerable.Range(0, 100), i => { Console.WriteLine(i); });
使用场景
对于数据的处理需要耗费较长时间的循环适宜使用并行循环,利用多线程加快执行速度。
对于简单的迭代操作,且迭代范围较小,使用常规循环更好好,因为并行循环涉及到线程的创建、上下文切换和销毁,使用并行循环反而影响执行效率。
对于迭代操作简单但迭代范围很大的情况,我们可以对数据进行分区,再执行并行循环,减少线程数量。
循环结果
Parallel.For和Parallel.ForEach方法的所有重载有着同样的返回值类型ParallelLoopResult,并行循环结果包含循环是否完成以及最低迭代次数两项信息。
下面的例子使用Parallel.ForEach展示了并行循环的结果。
ParallelLoopResult result = Parallel.ForEach(Enumerable.Range(0, 100), (i,loop) =>
{// 委托传入ParallelLoopState,用来控制循环执行
Console.WriteLine(i + 1);
Thread.Sleep(100);
if (i == 30) // 此处设置循环停止的确切条件
{
loop.Break();
//loop.Stop();
}
});
Console.WriteLine($"{result.IsCompleted}-{result.LowestBreakIteration}");
值得一提的是,循环的Break()和Stop()只能尽早地跳出或者停止循环,而不能立即停止。
取消循环操作
有时候,我们需要在中途取消循环操作,但又不知道确切条件是什么,比如用户触发的取消。这时候,可以利用循环的ParallelOptions传入一个CancellationToken,同时使用异常处理捕获OperationCanceledException以进行取消后的处理。下面是一个简单的例子。
/// <summary>
/// 取消通知者
/// </summary>
public static CancellationTokenSource CTSource { get; set; } = new CancellationTokenSource();
/// <summary>
/// 取消并行循环
/// </summary>
public static void CancelParallelLoop()
{
Task.Factory.StartNew(() =>
{
try
{
Parallel.ForEach(Enumerable.Range(0, 100), new ParallelOptions { CancellationToken = CTSource.Token },
i =>
{
Console.WriteLine(i + 1);
Thread.Sleep(1000);
});
}
catch (OperationCanceledException oce)
{
Console.WriteLine(oce.Message);
}
});
}
static void Main(string[] args)
{
ParallelDemo.CancelParallelLoop();
Thread.Sleep(3000);
ParallelDemo.CTSource.Cancel();
Console.ReadKey();
}
循环异常收集
并行循环执行过程中,可以捕获并收集迭代操作引发的异常,循环结束时抛出一个AggregateException异常,并将收集到的异常赋给它的内部异常集合InnerExceptions。外部使用时,捕获AggregateException,即可进行并行循环的异常处理。
下面的例子模拟了并行循环的异常抛出、收集及处理的过程。
/// <summary>
/// 捕获循环异常
/// </summary>
public static void CaptureTheLoopExceptions()
{
ConcurrentQueue<Exception> exceptions = new ConcurrentQueue<Exception>();
Parallel.ForEach(Enumerable.Range(0, 100), i =>
{
try
{
if (i % 10 == 0)
{//模拟抛出异常
throw new Exception($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] had thrown a exception. [{i}]");
}
Console.WriteLine(i + 1);
Thread.Sleep(100);
}
catch (Exception ex)
{//捕获并收集异常
exceptions.Enqueue(ex);
}
});
if (!exceptions.IsEmpty)
{// 方法内部可直接进行异常处理,若需外部处理,将收集到的循环异常抛出
throw new AggregateException(exceptions);
}
}
外部处理方式
try
{
ParallelDemo.CaptureTheLoopExceptions();
}
catch (AggregateException aex)
{
foreach (Exception ex in aex.InnerExceptions)
{// 模拟异常处理
Console.WriteLine(ex.Message);
}
}
分区并行处理
当循环操作很简单,迭代范围很大的时候,ParallelLoop提供一种分区的方式来优化循环性能。下面的例子展示了分区循环的使用,同时也能比较几种循环方式的执行效率。
/// <summary>
/// 分区并行处理,顺便比较各种循环的效率
/// </summary>
/// <param name="rangeSize">迭代范围</param>
/// <param name="opDuration">操作耗时</param>
public static void PartationParallelLoop(int rangeSize = 10000, int opDuration = 1)
{
//PartationParallelLoopWithBuffer
Stopwatch watch0 = Stopwatch.StartNew();
Parallel.ForEach(Partitioner.Create(Enumerable.Range(0, rangeSize), EnumerablePartitionerOptions.None),
i =>
{//模拟操作
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] was running. [{i}]");
Thread.Sleep(opDuration);
});
watch0.Stop();
//PartationParallelLoopWithoutBuffer
Stopwatch watch1 = Stopwatch.StartNew();
Parallel.ForEach(Partitioner.Create(Enumerable.Range(0, rangeSize),EnumerablePartitionerOptions.NoBuffering),
i =>
{//模拟操作
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] was running. [{i}]");
Thread.Sleep(opDuration);
});
watch1.Stop();
//NormalParallelLoop
Stopwatch watch2 = Stopwatch.StartNew();
Parallel.ForEach(Enumerable.Range(0, rangeSize),
i =>
{//模拟操作
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] was running. [{i}]");
Thread.Sleep(opDuration);
});
watch2.Stop();
//NormalLoop
Stopwatch watch3 = Stopwatch.StartNew();
foreach (int i in Enumerable.Range(0, rangeSize))
{//模拟操作
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] was running. [{i}]");
Thread.Sleep(opDuration);
}
watch2.Stop();
Console.WriteLine();
Console.WriteLine($"PartationParallelLoopWithBuffer => {watch0.ElapsedMilliseconds}ms");
Console.WriteLine($"PartationParallelLoopWithoutBuffer => {watch1.ElapsedMilliseconds}ms");
Console.WriteLine($"NormalParallelLoop => {watch2.ElapsedMilliseconds}ms");
Console.WriteLine($"NormalLoop => {watch3.ElapsedMilliseconds}ms");
}
在 I7-7700HQ + 16GB 配置 VS调试模式下得到下面一组测试结果。
| Loop Condition | PartationParallelLoop WithBuffer | PartationParallelLoop WithoutBuffer | Normal ParallelLoop | Normal Loop |
|---|---|---|---|---|
| 10000,1 | 10527 | 11799 | 11155 | 19434 |
| 10000,1 | 9513 | 11442 | 11048 | 19354 |
| 10000,1 | 9871 | 11391 | 14782 | 19154 |
| 100,1000 | 9107 | 5951 | 5081 | 100363 |
| 100,1000 | 9086 | 5974 | 5187 | 100162 |
| 100,1000 | 9208 | 5125 | 5255 | 100239 |
| 100,1 | 350 | 439 | 243 | 200 |
| 100,1 | 390 | 227 | 166 | 198 |
| 100,1 | 466 | 225 | 84 | 197 |
应该根据不同的应用场景选择合适的循环策略,具体如何选择,朋友们可自行体会~
C#并行编程(3):并行循环的更多相关文章
- .NET 并行编程——任务并行
本文内容 并行编程 任务并行 隐式创建和运行任务 显式创建和运行任务 任务 ID 任务创建选项 创建任务延续 创建分离的子任务 创建子任务 等待任务完成 组合任务 任务中的异常处理 取消任务 Task ...
- .NET 并行编程——数据并行
本文内容 并行编程 数据并行 环境 计算 PI 矩阵相乘 把目录中的全部图片复制到另一个目录 列出指定目录中的所有文件,包括其子目录 最近,对多线程编程,并行编程,异步编程,这三个概念有点晕了,之前我 ...
- 一、并行编程 - 数据并行 System.Threading.Tasks.Parallel 类
一.并行概念 1.并行编程 在.NET 4中的并行编程是依赖Task Parallel Library(后面简称为TPL) 实现的.在TPL中,最基本的执行单元是task(中文可以理解为"任 ...
- Delphi XE7并行编程: 并行For循环
从Delphi XE7开始,引入了全新的并行编程库用于简化并行编程,它位于System.Threading单元中. 下面是一个判断素数的简单例子:function IsPrime (N: Intege ...
- .NET并行编程1 - 并行模式
设计模式——.net并行编程,清华大学出版的中译本. 相关资源地址主页面: http://parallelpatterns.codeplex.com/ 代码下载: http://parallelpat ...
- 【读书笔记】.Net并行编程(三)---并行集合
为了让共享的数组,集合能够被多线程更新,我们现在(.net4.0之后)可以使用并发集合来实现这个功能.而System.Collections和System.Collections.Generic命名空 ...
- .Net多线程 并行编程(三)---并行集合
为了让共享的数组,集合能够被多线程更新,我们现在(.net4.0之后)可以使用并发集合来实现这个功能. 而System.Collections和System.Collections.Generic命名 ...
- C#并行编程中的Parallel.Invoke
一.基础知识 并行编程:并行编程是指软件开发的代码,它能在同一时间执行多个计算任务,提高执行效率和性能一种编程方式,属于多线程编程范畴.所以我们在设计过程中一般会将很多任务划分成若干个互相独立子任务, ...
- Parallel并行编程
Parallel并行编程 Parallel并行编程可以让我们使用极致的使用CPU.并行编程与多线程编程不同,多线程编程无论怎样开启线程,也是在同一个CPU上切换时间片.而并行编程则是多CPU核心同时工 ...
- C#并行编程
C#并行编程中的Parallel.Invoke 一.基础知识 并行编程:并行编程是指软件开发的代码,它能在同一时间执行多个计算任务,提高执行效率和性能一种编程方式,属于多线程编程范畴.所以我们在设计过 ...
随机推荐
- 初识java和C的不同
学习java语言,发现C语言的语法尽管很类似,但是java的代码编写与C语言却大不相同. 一,java的类,首先接触到的是类这个思想,类中可以定义属性,可以用方法来对属性进行相应的操作: 二,jav ...
- 实例详析ImageView的adjustViewBonds和scaleType
android:adjustViewBounds是否保持宽高比.需要与maxWidth.MaxHeight一起使用,否则单独使用没有效果. 设置View的最大高度,单独使用无效,需要与setAdjus ...
- mac系统在配置navicat时连接数据的时候提示can't connect to mysql server on '127.0.0.1'
新建数据库连接的时候,将默认的端口号更改掉,改为3307,即可解决这个问题. 具体是为什么我也不清楚,我自己想的一个可能就是mac电脑 上的某个程序可能已经占用了3306那个默认的端口,因 ...
- c# 创建项目时提示:未能正确加载“microsoft.data.entity.design.bootstrappackage
vs 2005 ,vs 2008, vs 2010,安装后有时出现这个错误(我的机器装的x64的win7),很烦人.找了很多地方都不能解决.其实说起来还是开发国家牛,轻易就解决了这个问题.其实出现这个 ...
- UML和模式应用5:细化阶段(5)---系统顺序图
1.前言 系统顺序图(SSD)是为阐述系统相关的输入和输出事件而快速.简单的创建的制品,它们是操作契约和对象设计的输入. SSD展示了直接与系统交互的外部参与者.系统(作为黑盒)以及由参与者发起的系统 ...
- 半自动代码生成--方式C#
寻找半自动代码生成方式基于C#的GUI或者其它: 1. (推荐)Millennials - A Custom Source Code Generator https://www.codeprojec ...
- windows系统下安装tomcat及配置
1.安装测试 1.安装 推荐使用免安装版的Tomcat(放在没有中文和空格的目录下),前提是已经安装了JDK并配置了环境变量. 2.测试 双击startup.bat,浏览器输入url:localhos ...
- 取消Eclipse控制台显示行数的限制
--------------------------------------------------------------------------------------------------- ...
- TomCat安装配置教程
一.JDK的安装与配置 1.从官网下载jdk,注意是jdk不是jre.最好从官网下载,也可以直接度娘. 2.下载完毕后,安装jdk,直接按照安装向导的提示安装即可,安装时可以自己选择安装路径,我的安 ...
- IntelliJ IDEA2017 使用教程
一:安装教程 请参考<Windows7下安装与破解IntelliJ IDEA2017> 二:目录说明 三:开发界面