C#--在多线程中使用任务并行库(TPL)--15

目录

一.任务并行库的概念以及定义

二.主要特性

三.代码使用示例

1.最基础的Parallel.For使用方式

2.使用 ParallelOptions 来控制并行执行

3.Parallel.ForEach的使用(用于处理集合)

4.带有本地变量的并行循环(用于需要累加或统计的场景)

5.结合Task和Parallel的高级示例

6.使用ParallelOptions结合异步操作

总结


一.任务并行库的概念以及定义

在C#中,任务并行库(Task Parallel Library,简称TPL)是.NET Framework和.NET Core中提供的一组用于简化并行编程和异步编程的公共类型和API,旨在帮助开发人员更容易的利用多核处理器的能力,提高应用程序的性能和响应速度

任务(Task):

TPL中的核心概念是任务,它表示一个异步操作,可以理解为某项工作或操作的抽象封装.

任务可以异步的执行,并可以返回结果,报告进度,处理取消和异常等

数据并行(Data Parallelism):

通过并行执行集合中的各个元素的操作,如使用Parallel.For和Parallel.ForEach方法,可以同时处理大量数据,提高数据处理的效率

任务并行(Task Parallelism):

利用任务来并发的执行不同的方法或操作,适用于多个操作相互独立,可以同时执行的场景

二.主要特性

  • 1.简化并行编程:TPL提供了更高级别的抽象,开发者无需直接管理线程的创建,同步等复杂细节
  • 2.任务调度:内置的任务调度器会智能地将任务映射到线程池中的线程上,优化资源使用
  • 3.异常处理:提供了机制来捕获和处理并行执行中产生的异常,确保应用程序的稳定性
  • 4.任务组合:支持将多个任务链接到一起,形成任务的连续执行和组合(如 ContinueWith方法)
  • 5.取消和超时:提供了取消令牌(Cancellation Token),允许在需要时取消任务的执行
  • 6.异步和编程支持:与 async 和 await 关键字结合使用,可以编写简单的异步代码,提升应用程序的响应性

三.代码使用示例

1.最基础的Parallel.For使用方式

 Console.WriteLine("\n基础的 Parallel.For 示例:");
 // 这里的 (0, 5) 表示从0开始到4结束(不包含5)
 // i => { } 是一个委托,表示对每个数字要执行的操作
 Parallel.For(0, 5, i =>
 {
     Console.WriteLine($"正在并行处理数字 {i}");
     Thread.Sleep(100); // 模拟一些耗时的工作
 });

代码输出结果示例:

因为是并行处理,所以执行顺序不是固定的.

2.使用 ParallelOptions 来控制并行执行

在C#中, ParallelOptions 是用于配置并行操作行为的选项类,主要用于控制Parallel类提供的并行循环和任务的执行方式

Parallel类包含如:Parallel.For ,Parallel.ForEach和Parallel.Invoke等方法.它们可以利用多核处理器并行执行任务

ParallelOptions的主要属性:

  • MaxDegreeOfParallelism:指定并行操作的最大并行度,即同时运行的最大线程数,默认情况下MaxDegreeOfParallelism为-1,表示不限制并行度,线程数由.NET框架根据可用的处理器核心数量自动调度
  • CancellationToken:用于接收取消请求的令牌,可以在并行操作中响应取消操作.支持任务的取消操作,当需要在某些条件下中止并行任务时,可以使用CancellationTokenSource生成令牌,并在操作中监视CancellationToken的取消请求
  • TaskScheduler:指定任务的调度器,控制任务的执行上下文,如果不设置默认使用TaskScheduler.Default
Console.WriteLine("\n使用 ParallelOptions 的 Parallel.For:");
// 创建一个 CancellationTokenSource,用于发送取消请求
CancellationTokenSource cts = new CancellationTokenSource();
//配置ParallelOptions
var options = new ParallelOptions
{
    // Environment.ProcessorCount 获取CPU的核心数
    // MaxDegreeOfParallelism 控制最大同时执行的任务数
    MaxDegreeOfParallelism = 2,// 这里限制最多同时执行2个任务
    CancellationToken = cts.Token // 将取消令牌传递给并行循环
};

try
{
    // 使用配置了选项的并行循环
    Parallel.For(0, 5, options, i =>
    {
        Console.WriteLine($"使用受限并行度处理数字 {i},线程Id: {Task.CurrentId}");
        // 可选:在工作中检查取消请求
        options.CancellationToken.ThrowIfCancellationRequested();
        Thread.Sleep(100);
        Console.WriteLine($"完成处理索引 {i}");
    });
}
catch (OperationCanceledException)
{

    Console.WriteLine("操作已被取消。");
}
finally
{
    cts.Dispose();
}

代码输出结果示例:

代码中限制了最多同时执行两个线程的任务

当两个线程中的其中一个线程完成任务并释放后才会处理下一个任务

3.Parallel.ForEach的使用(用于处理集合)

  Console.WriteLine("\n使用 Parallel.ForEach 示例:");
  // 创建一个简单的字符串列表
  List<string> fruits = new List<string>
  {
      "苹果", "香蕉", "橙子", "葡萄"
  };

  // 并行处理列表中的每一项
  Parallel.ForEach(fruits, fruit =>
  {
      Console.WriteLine($"正在处理水果: {fruit},线程Id: {Task.CurrentId}");
      Thread.Sleep(100);
      Console.WriteLine($"完成处理水果: {fruit}");
  });

代码示例中的Parallel.ForEach方法概述:

第一个参数: fruits

  • 类型: IEnumberable<T>,在这是时IEnumberable<string>
  • 代表要迭代的集合,也就是我们要并行处理的元素序列(在当前示例中指代fruits)

第二个参数: fruit=>{...}

  • 类型:Action<T> 这里是Action<string>
  • 这是一个委托,表示要对每个元素执行的操作.在这里我们使用了 Lambda表达式 定义这个操作
  • fruit: Lambda表达式的参数,代表当前正在处理的集合元素.在每次的迭代中,fruit将被赋值为fruits集合中的一个元素

代码输出结果示例:

4.带有本地变量的并行循环(用于需要累加或统计的场景)

  Console.WriteLine("\n带本地变量的 Parallel.For 示例:");
  int sum = 0; // 定义一个全局计数器
  Parallel.For(0, 10,
      // 初始化方法:为每个并行任务创建一个本地计数器
      () => 0,

      // 本体方法:处理每个数字并更新本地计数器
      (i, loop, localSum) =>
      {
          localSum += i; // 将当前数字加到本地计数器
          Console.WriteLine($"线程 {Task.CurrentId} 处理数字 {i}, 当前本地和为 {localSum}");
          return localSum; // 返回更新后的本地计数器
      },

      // 最终方法:处理每个线程的本地计数器最终值
      (finalLocalSum) =>
      {
          Console.WriteLine($"一个线程完成{Task.CurrentId},最终本地和为: {finalLocalSum}");
          Interlocked.Add(ref sum, finalLocalSum); // 使用原子操作将本地计数器加到全局计数器,确保线程安全,避免竞态条件
      }
  );
Console.WriteLine($"所有线程完成,最终和为: {sum}");

代码概述:使用Parallel.For进行并行循环,通过引用线程本地变量,累加从0到9的数字,并在每个线程完成时输出其本地计算的总和

当前代码使用的Parallel.For的完整签名为:

public static ParallelLoopResult For<TLocal>(
    int fromInclusive,
    int toExclusive,
    Func<TLocal> localInit,
    Func<int, ParallelLoopState, TLocal, TLocal> body,
    Action<TLocal> localFinally
)

参数解析:

  • fromInclusive:(起始索引,包含)
    • 类型: int
    • 作用:并行循环的起始索引,包含该值
  • toExclusive:(结束索引,不包含)
    • 类型:int
    • 作用:并行循环的结束索引,不包含该值
  • localInit(本地初始化器):
    • 类型:Func<TLocal>
    • 作用:一个函数,定义了每个线程(任务)的本地变量的初始值
    • 在示例代码中()=>0,表示每个线程的本地计数器初始值为0
  • body(循环主体):
    • 类型:Func<int,ParallelLoopState,TLocal,TLocal>
      • 参数说明:
        • int i:当前处理的元素索引
        • ParallelLoopState:用于控制循环的状态(如中断,停止)
        • TLocal local 线程的本地变量
      • 返回值 TLocal,即更新后的本地变量
      • 作用:定义了并行循环中要执行的操作,并可以更新本地变量
        (i, loop, localSum) =>
        {
            localSum += i;
            Console.WriteLine($"线程 {Task.CurrentId} 处理数字 {i}, 当前本地和为 {localSum}");
            return localSum;
        }
        
        在示例代码中:
        • 将当前索引i加到本地计数器localSum中
        • 输出当前线程处理的数字和(本地和)
        • 返回更新后的localSum
    • localFinally(本地最终操作)
      • 类型:Action<TLocal>
      • 作用:定义每个线程完成其任务后执行的动作,接收该线程的最终本地变量值

 代码执行流程:

1.初始化阶段:

  • 并行循环开始前,Parallel.For会为每个参与的线程调用localInit函数,初始化本地变量
  • 在示例代码中,每个线程的localSum的初始值为0

2.并行执行阶段:

  • 对于从0到9的每个索引i,Parallel.For会并行执行body函数
  • 线程轮流或同时处理不同的i值,更新其各自的localSum
  • 在body函数中
    • localSum+=i; 将当前的索引i加到本地变量localSum中
    • return localSum:返回更新后的本地变量,以便在下一次循环中使用

3.最终处理阶段:

  • 当一个线程完成了其被分配的索引之后,Parallel.For会调用localFinally函数,传递该线程的最终localSum值.

代码输出结果示例:

 根据以上的输出结果,我们可以注意到线程35处理了两个数字

第一次处理数字5时,局部和是5

接着又处理了数字9,局部和更新为5+9=14

由此我们可以得知,在并行执行中,任务的分配可能并不均匀,某些线程可能会被分配到多个任务(数字),因此会出现局部和超过单个数字值的情况

在示例代码中,存在 Interlocked.Add(ref sum,finalLocalSum)

这是一个线程安全的代码,用于在多线程的环境下安全地修改变量的值,避免竞态条件

Interlocked.Add是一种用于实现原子操作的同步机制,可以确保多个线程在并发修改同意变量时,不会产生竞态条件,从而保证数据的一致性和正确性,通过使用这样的原子操作,可以在多线程环境中安全的更新共享变量

5.结合Task和Parallel的高级示例
 

Console.WriteLine("\nTask和Parallel结合使用示例:");

// 创建一个任务数组,每个任务使用Parallel处理不同的数据集
var tasks = new List<Task>();

// 创建第一个并行处理任务
tasks.Add(Task.Run(() =>
{
    Parallel.For(0, 5, i =>
    {
        Console.WriteLine($"任务1处理数字 {i}, 线程Id: {Task.CurrentId}");
        Thread.Sleep(50);
    });
}));

// 创建第二个并行处理任务
tasks.Add(Task.Run(async () =>
{
    await Task.Delay(100); // 模拟一些异步操作
    Parallel.ForEach(new[] { "数据1", "数据2", "数据3" }, item =>
    {
        Console.WriteLine($"任务2处理项目: {item}, 线程Id: {Task.CurrentId}");
        Thread.Sleep(50);
    });
}));

// 等待所有任务完成
await Task.WhenAll(tasks);
Console.WriteLine("示例5所有任务已完成");

代码概述 :

通过Task和Parallel结合,实现多个(两个)任务下的并行处理

代码输出结果示例:

6.使用ParallelOptions结合异步操作

 Console.WriteLine("\n带有异步操作的Parallel示例:");
 var parallelOptions = new ParallelOptions
 {
     MaxDegreeOfParallelism = Environment.ProcessorCount//  为 CPU 核心数(自动去调度剩余空闲CPU)
 };

 await Task.Run(() =>
 {
     Parallel.For(0, 3, parallelOptions, async i =>
     {
         await Task.Delay(100); // 异步等待
         Console.WriteLine($"异步并行处理索引 {i}, 线程Id: {Environment.CurrentManagedThreadId}"); //通过Environment.CurrentManagedThreadId获取线程Id
     });
 });

 Console.WriteLine("示例6所有操作已完成");

 代码输出结果示例:

在输出结果中先打印"示例6所有操作已完成"说明Task中的任务并行任务在异步执行

总结

任务并行库(TPL)提供了丰富的方法来简化多线程和异步编程:

  • Parallel.For和Parallel.ForEach:用于并行执行循环或遍历集合
  • ParallelOptions:允许配置并行操作的行为,例如最大并行度和取消操作支持
  • 本地变量:在并行操作中使用本地变量,可以避免线程间的数据竞争,提高性能和安全性
  • 结合Task:通过并行操作嵌套在任务中,可以创建复杂的并行和异步流程

在使用TPL中,需要注意以下几点:

  • 线程安全:在并行操作中访问共享资源时,需确保线程安全,使用锁或线程安全的操作(如Interlocked类)
  • 资源管理:过高的并行度可能导致资源竞争和性能下降,应根据实际情况设置合适的并行长度
  • 异常处理:并行操作中的异常需要去妥善的处理,避免在线程崩溃导致程序不稳定
  • 取消和超时:使用CancellationToken支持任务的取消,在需要能够及时中断长时间的并行操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/956483.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

与“神”对话:Swift 语言在 2025 中的云霓之望

0. 引子 夜深人静&#xff0c;是一片极度沉醉的黑&#xff0c;这便于我与深沉的 macbook 悄悄隐秘于其中。一股异香袭来&#xff0c;恍惚着&#xff0c;撸码中身心极度疲惫、头脑昏沉的我仿佛感觉到了一束淡淡的微光轻洒在窗边。 我的对面若隐若现逐渐浮现出一个熟悉的身影。他…

WOA-Transformer鲸鱼算法优化编码器时间序列预测(Matlab实现)

WOA-Transformer鲸鱼算法优化编码器时间序列预测&#xff08;Matlab实现&#xff09; 目录 WOA-Transformer鲸鱼算法优化编码器时间序列预测&#xff08;Matlab实现&#xff09;预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现WOA-Transformer鲸鱼算法优化编…

基于SpringBoot和PostGIS的各国及所属机场信息检索及可视化实现

目录 前言 一、空间数据简介 1、全球国家信息表 2、机场信息表 3、国家机场检索实现 二、SpringBoot后台实现 1、模型层实现 2、控制层实现 三、WebGIS可视化实现 1、Leaflet界面实现 2、国家及其机场可视化成果 3、全球机场数量排行榜 四、总结 前言 新春佳节即将…

MLMs之Agent:Phidata的简介、安装和使用方法、案例应用之详细攻略

MLMs之Agent&#xff1a;Phidata的简介、安装和使用方法、案例应用之详细攻略 目录 Phidata简介 Phidata安装和使用方法 1、安装 2、使用方法 (1)、认证 (2)、创建 Agent (3)、运行 Agent (4)、Agent Playground Phidata 案例应用 1、多工具 Agent 2、多模态 Agent …

【机器学习实战入门项目】使用深度学习创建您自己的表情符号

深度学习项目入门——让你更接近数据科学的梦想 表情符号或头像是表示非语言暗示的方式。这些暗示已成为在线聊天、产品评论、品牌情感等的重要组成部分。这也促使数据科学领域越来越多的研究致力于表情驱动的故事讲述。 随着计算机视觉和深度学习的进步&#xff0c;现在可以…

【ArcGIS微课1000例】0140:总览(鹰眼)、放大镜、查看器的用法

文章目录 一、总览工具二、放大镜工具三、查看器工具ArcGIS中提供了三种局部查看的工具: 总览(鹰眼)、放大镜、查看器,如下图所示,本文讲述这三种工具的使用方法。 一、总览工具 为了便于效果查看与比对,本实验采用全球影像数据(位于配套实验数据包中的0140.rar中),加…

从零搭建一套远程手机的桌面操控和文件传输的小工具

从零搭建一套远程手机的桌面操控和文件传输的小工具 --ADB连接专题 一、前言 前面的篇章中&#xff0c;我们确定了通过基于TCP连接的ADB控制远程手机的操作思路。本篇中我们将进行实际的ADB桥接的具体链路搭建工作&#xff0c;从原理和实际部署和操作层面上&#xff0c;从零…

ROS2 与机器人视觉入门教程(ROS2 OpenCV)

系列文章目录 前言 由于现有的ROS2与计算机视觉&#xff08;特别是机器人视觉&#xff09;教程较少&#xff0c;因此根据以往所学与积累的经验&#xff0c;对ROS2与机器人视觉相关理论与代码进行分析说明。 本文简要介绍了机器人视觉。首先介绍 ROS2 中图像发布者和订阅者的基…

JVM 面试八股文

目录 1. 前言 2. JVM 简介 3. JVM 内存划分 3.1 为什么要进行内存划分 3.2 内存划分的核心区域 3.2.1 核心区域一: 程序计数器 3.2.2 核心区域二: 元数据区 3.2.3 核心区域三: 栈 3.2.4 核心区域四: 堆 4. JVM 类加载机制 4.1 类加载的步骤 4.1.1 步骤一: 加载 4…

我的世界-与门、或门、非门等基本门电路实现

一、红石比较器 (1) 红石比较器结构 红石比较器有前端单火把、后端双火把以及两个侧端 其中后端和侧端是输入信号,前端是输出信号 (2) 红石比较器的两种模式 比较模式 前端火把未点亮时处于比较模式 侧端>后端 → 0 当任一侧端强度大于后端强度时,输出…

【2024年华为OD机试】 (B卷,100分)- 字符串分割(Java JS PythonC/C++)

一、问题描述 题目解析 问题描述 给定一个非空字符串 s&#xff0c;要求将该字符串分割成若干子串&#xff0c;使得每个子串的 ASCII 码值之和均为“水仙花数”。具体要求如下&#xff1a; 若分割不成功&#xff0c;则返回 0&#xff1b;若分割成功且分割结果不唯一&#x…

Elasticsearch 和arkime 安装

安装一定要注意版本号&#xff0c;不然使用不了 这里Ubuntu使用ubuntu-20.04.6-desktop-amd64.iso elasticsearch这里使用Elasticsearch 7.17.5 | Elastic arkime这里使用wget https://s3.amazonaws.com/files.molo.ch/builds/ubuntu-20.04/arkime_3.4.2-1_amd64.deb 大家想…

简述mysql 主从复制原理及其工作过程,配置一主两从并验证。

MySQL 主从同步是一种数据库复制技术&#xff0c;它通过将主服务器上的数据更改复制到一个或多个从服务器&#xff0c;实现数据的自动同步。 主从同步的核心原理是将主服务器上的二进制日志复制到从服务器&#xff0c;并在从服务器上执行这些日志中的操作。 MySQL主从同步是基…

MySQL 主从复制原理及其工作过程的配置

一、MySQL主从复制原理 MySQL 主从同步是一种数据库复制技术&#xff0c;它通过将主服务器上的数据更改复制到一个或多个从服务器&#xff0c;实现数据的自动同步。 主从同步的核心原理是将主服务器上的二进制日志复制到从服务器&#xff0c;并在从服务器上执行这些日志中的操作…

多平台下Informatica在医疗数据抽取中的应用

一、引言 1.医疗数据抽取与 Informatica 概述 1.1 医疗数据的特点与来源 1.1.1 数据特点 医疗数据具有显著的多样性特点。从数据类型来看&#xff0c;涵盖了结构化数据&#xff0c;如患者的基本信息、检验检查结果等&#xff0c;这些数据通常以表格形式存储&#xff0c;便于…

智能创造的幕后推手:AIGC浪潮下看AI训练师如何塑造智能未来

文章目录 一、AIGC时代的算法与模型训练概览二、算法与模型训练的关键环节三、AI训练师的角色与职责四、AI训练师的专业技能与素养五、AIGC算法与模型训练的未来展望《AI训练师手册&#xff1a;算法与模型训练从入门到精通》亮点内容简介作者简介谷建阳 目录 《AI智能化办公&am…

有限元分析学习——Anasys Workbanch第一阶段笔记(13)网格单元分类、物理场与自由度概念

目录 0 序言 1 网格单元分类 2 各类单元的应用 3 massage与帮助和查看 4 物理场和自由度 4.1 各种单元自由度 4.2 结构自由度 0 序言 本章主要讲解网格单元的分类及物理场和自由度的相关概念。 1 网格单元分类 按单元的形状分类&#xff1a;实体单元、壳单元和杆梁单元…

RC2在线加密工具

RC2是由著名密码学家Ron Rivest设计的一种传统对称分组加密算法&#xff0c;它可作为DES算法的建议替代算法。RC2是一种分组加密算法&#xff0c;RC2的密钥长度可变&#xff0c;可以从8字节到128字节&#xff0c;安全性选择更加灵活。 开发调试上&#xff0c;有时候需要进行对…

深度学习笔记——循环神经网络RNN

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍面试过程中可能遇到的循环神经网络RNN知识点。 文章目录 文本特征提取的方法1. 基础方法1.1 词袋模型&#xff08;Bag of Words, BOW&#xff09;工作原…

.NET周刊【1月第1期 2025-01-05】

国内文章 3款.NET开源、功能强大的通讯调试工具&#xff0c;效率提升利器&#xff01; https://www.cnblogs.com/Can-daydayup/p/18631410 本文介绍了三款功能强大的.NET开源通讯调试工具&#xff0c;旨在提高调试效率。这些工具包括LLCOM&#xff0c;提供串口调试和自动化处…