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垃圾回收机制复习
			一.如何确定某个对象是“垃圾” 二.典型的垃圾收集算法 三.典型的垃圾收集器 JVM(HotSpot) 7种垃圾收集器的特点及使用场景 https://www.cnblogs.com/chengxuy ... 
- MGR架构 ~ MGR+proxysql(2)
			一 简介: 上篇环境已经搭建完成,我们开始进行测试 二 工具和环境: sysbench ,mgr+proxysql 三 测试方式: sysbench+oltp.lua脚本 四 模拟故障 1 并发环境观 ... 
- Python提示AttributeError 或者DeprecationWarning: This module was deprecated解决方法
			Python提示AttributeError 或者DeprecationWarning: This module was deprecated解决方法 在使用Python的sklearn库时,发现sk ... 
- mysql一次查询,返回多个统计结果
			1.sum(if) select sum(if(status=1,1,0)) as s1_count,sum(if(status=2,1,0)) as s2_countfrom order; 2.co ... 
- java生成TXT
			1.由于公司需要生成如下格式TXT: var ovr_parameters={ "changeMainVideoList": [ ], "indexList": ... 
- Pycharm 字体大小调整
			Pycharm 字体大小调整 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/awyyauqpmy/article/details/79334496P ... 
- linux中fork()函数详解【转】
			转自:http://blog.csdn.net/jason314/article/details/5640969 一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过 ... 
- openstack swift节点安装手册2-创建rings
			以下步骤需要在controller节点上进行操作: 切换到/etc/swift目录下进行如下操作: 一.创建account ring 1.创建account.builder文件 swift-ring- ... 
- python用win32pdh模块查看进程信息
			import win32pdh def get_processes(): win32pdh.EnumObjects(None, None, win32pdh.PERF_DETAIL_WIZARD) # ... 
- 超图(Hypergraph)
			原文地址:http://blog.csdn.net/qrlhl/article/details/48413117 超图(Hypergraph)是什么 简单的来说,对于我们熟悉的图而言,它的一个边(ed ... 
