多线程7:使用PLINQ
1、简介
(1) 任务并行库
并行库通常称为并行框架扩展(Parallel Framework Extensions,简称PFX),包含三大主要部分:
- 任务并行库(TPL)
- 并发集合
- 并行LINQ(或PLINQ)
(2) 任务并行与数据并行
将程序分割成一组任务并使用不同的线程来运行不同的任务,这种方式称为任务并行(task parallelism)。
将数据分割成较小的数据块,对这些数据快进行并行计算,然后聚合这些计算结果,这种编程模型称为数据并行(data parallelism)。
(3) PLINQ
PLINQ具有最高级抽象。它自动将数据分割为数据块,并且决定是否真的需要并行化查询,或者使用通常的顺序查询处理更高效。
PLINQ的基础设施会将分割任务的执行结果组合到一起。
2、使用Parallel类
Parallel.Invoke方法会阻塞其他线程直到所有的任务都完成。
使用Parallel.For和Parallel.Foreach方法来定义循环。
并行Foreach循环可以通过给每个集合项应用一个action委托的方式,实现并行地处理任何IEnumerable集合。
通过ParallelOptions类实例,可以使用CancellationToken取消循环,限制最大并行度(并行最大操作数),还可提供自定义TaskScheduler类来调度任务。
Action可以接受一个ParallelLoopState参数,用于从循环中跳出或者检查当前循环的状态。
ParallelLoopState有两种方式停止并行循环:
- Stop方法告诉循环停止处理任何工作,并设置并行循环状态属性IsStopped为true。
- Break方法停止其之后的迭代,但之前的迭代还要继续工作。循环结果的LowestBreakIteration属性将会包含当Break方法被调用时的最低循环次数。
3、并行化LINQ查询
(1) 默认的LINQ查询是串行的,即同步执行,所有操作都运行在当前线程。
(2) 使用AsParallel方法可以获取ParallelQuery<T>对象,它包含了PLINQ的逻辑实现,并且作为IEnumerable集合的一组扩展方法。
ParallelQuery<T>以并行的方式运行,默认情况下结果会被合并到单个线程中。
(3) ParallelQuery<T>.ForAll方法执行与任务被处理的线程是同一个,跳过了结果合并步骤。
(4) AsSequential方法可以将PLINQ查询以顺序方式运行,所有操作都运行在当前线程。
4、调整PLINQ查询的参数
(1) ParallelQuery.WithCancellation方法可传入取消标志对象,取消时会抛出OperationCanceledException异常,并取消剩余的工作。
(2) WithDegreeOfParallelism方法可以指定并行度,即用于执行查询时实际并行分割数。
如果PLINQ基础设施决定最好使用较少的并行度以节省资源和提高性能,那么并行度会小于最大值。
(3) WithExecutionMode方法用来设置查询执行的模式。
PLINQ基础设施如果认为并行化某查询只会增加工作量并且运行更慢,那么会以顺序模式执行该查询。
但是可以强制该查询以并行的方式运行。
(4) WithMergeOptions方法调整对查询结果的处理。
默认模式是PLINQ基础设施在查询返回结果之前会缓存一定数量的结果。
如果花费了大量的时间,更合理的方式是关闭结果缓存从而尽可能快地得到结果。
(5) AsOrdered方法告诉PLINQ基础设施按项在集合中的顺序来进行处理。
默认的情况是,使用并行执行时,集合中的项有可能不是被顺序处理的,集合中稍后的项可能会比稍前的项先处理。
5、处理PLINQ查询中的异常
PLINQ查询期间发生的所有异常,PLINQ基础设施会抛出AggregateException异常,可使用Flatten和Handle方法处理内部的异常。
处理聚合异常(AggregateException)时,如果不处理里面的所有异常,该异常就会向上传递导致应用程序停止工作。
6、管理PLINQ查询中的数据分区
通过Partitioner<T>标准基类可派生出自定义的PLINQ查询分区策略类,用于并行处理不同分区的数据集合。
重载SupportsDynamicPartitions属性并设置其值为false可以声明仅支持静态分区。
静态分区如果集合中的数据分布不均匀,会造成分区元素少的处理线程过早完成任务而不会帮助元素多的分区处理数据。
动态分区意味着可以实时对初始集合进行分区,并在工作者线程间平衡工作负载。
创建Partitioner需要在参数中指定足够的分区,否则Partitioner会返回不正确号码的分区错误。
7、为PLINQ查询创建一个自定义的聚合器
一个PLINQ查询会被多个任务同时以并行的方式处理,那么就需要提供一种机制来并行地聚合每个任务的结果,,然后将这些聚合的值合并到单个结果值中。
ParallelEnumerable类中的Aggregate扩展方法可以聚合PLINQ查询的结果,它接受4个参数,每个参数都是用于执行聚合过程不同部分的函数:
- 第1个参数是一个工厂方法,构造了该聚合器的空初始值,该值也被称为种子值。
注意:Aggregate方法的第1个参数值并不是聚合功能的初始种子值,而是一个构造了该初始种子值的工厂方法。
如果只提供一个实例,其将被使用在并行运行的所有分区中,这将导致不正确的结果。
- 第2个参数函数将每个集合项聚合到分区聚合对象中,这里的聚合对象在并行运行的每个查询分区中是不一样的。
- 第3个参数函数是一个高阶聚合函数,用于从分区中将一个聚合对象合并到全局聚合对象中。
- 第4个参数函数是一个选择器函数,指定了全局聚合对象中我们需要的确切数据。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
2020-05-28 C#编码转换