C#多线程(4)——任务并行库TPL

news2025/1/16 21:45:15

文章目录

      • 1 什么是TPL
      • 2 创建与启动任务
      • 3 等待任务
      • 4 任务中的异常处理
      • 5 取消任务

1 什么是TPL

T P L \textcolor{red}{TPL} TPL(Task Parallel Library)任务并行库,是从.NetFramwork4.0后引入的基于异步操作的一组API。TPL的底层是基于多线程实现的,但是它相较于直接使用多线程,更为简单,它向程序员隐藏了与线程池交互的底层代码。在.NetFramwork4.0后,微软更推荐程序员使用TPL去编写多线程代码或者并行代码。
TPL的核心是任务,一个任务代表了一个异步操作,该操作可以使用或不适用独立的线程运行。
一个任务可以和其他的任务组合起来,比如同时启动多个任务,等待所有的任务完成;对之前所有任务的结果进行计算,TPL的优势在于具有组合任务API,而不用单独书写线程同步的代码(关注于锁、线程间的信号)。同样在多线程中关于多线程中异常的传播与处理是极为复杂的,而在TPL中,可以通过 A g g r e g a t e E x c e p t i o n \textcolor{red}{AggregateException} AggregateException,捕获底层任务的所有异常,并允许单独处理这些异常。

2 创建与启动任务

创建、启动任务使用到的关键类如下:类定义在 S y s t e m . T h r e a d i n g . T a s k s \textcolor{red}{System.Threading.Tasks} System.Threading.Tasks 命名空间中
在这里插入图片描述

       public static void Main(string[] args)
        {
            void TaskMethod(string name) {
                Console.WriteLine("Task{0} is runing on ThreadId {1} Is ThreadPool Thread {2}",
                    name,
                    Thread.CurrentThread.ManagedThreadId,
                    Thread.CurrentThread.IsThreadPoolThread);
            }


            int  TaskMethod_1(string name) {

                Console.WriteLine("Task{0} is runing on ThreadId {1} Is ThreadPool Thread {2}",
                     name,
                     Thread.CurrentThread.ManagedThreadId,
                     Thread.CurrentThread.IsThreadPoolThread);

                return 0;
            }


            #region 1 创建任务
            TaskMethod("Main");
            //public Task(Action action) 传入一个无返回的委托函数
            Task t1 = new Task(() => TaskMethod("t1"));
            Task t2 = new Task(() => TaskMethod("t2"));
            t1.Start();//显示创建Task需要运行start方法才能运行任务中的方法
            t2.RunSynchronously(); //RunSynchronously()在当前线程上运行任务方法
            Task.Run(() => TaskMethod("t3"));
            Task.Factory.StartNew(() => TaskMethod("t4"));


            //Task<T> 管理有返回值的工作单元  public Task(Func<TResult> function)
            //通过传入有返回的委托函数来创建任务对象
            Task<int> t5 = new Task<int>(() => TaskMethod_1("t5"));
            t5.Start();
            Console.WriteLine(t5.Result); //获得任务结果

            Console.ReadKey();
            #endregion

        }

在这里插入图片描述

3 等待任务

有以下方式可以等待任务完成:

  • 调用Wait方法(可选择指定超时时间)
  • 访问Result属性(当使用Task时)
  • Task.WaitAll(等待所有指定任务完成)
  • Task.WaitAny(等待任意一个任务完成)。
            #region 2 等待任务
         
            Console.OutputEncoding = Encoding.Unicode;
            Console.WriteLine("起始执行时间为{0}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

            List<Task> list = new List<Task>();
            
            for (int i = 1; i <= 5; i++)
            {
                int tempI = i; //需要使用局部变量,因为for循环在数据量很小的时候,for循环结束时 task启动了,但是可能还未执行。由于共享变量i,所有在真正执行Task时,线程名称将一样 i=11
                Task t = new Task(()=>TaskMethod_2("-"+ tempI, tempI));
                list.Add(t);
                t.Start();//因为多线程的启动并不意味着立马进行,需要等待操作系统的调度。
            }

            Task.WaitAll(list.ToArray());//等待所有的线程完成
            Task.WaitAny(list.ToArray());//等待任意一个线程完成后执行,相当于在一个ManualResetEventSlim上等待,

            Console.WriteLine("等待所有任务线程执行完成,结束执行时间为{0}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));


            Task<int> t_1 = new Task<int>(() => TaskMethod_2("t_1", 10));
            t_1.Start();
            t_1.Wait();//主线程等待t_1线程完成任务方法。 Wait(TimeSpan timeout) 也可以等待具体的时间
            Console.WriteLine(t_1.Result);

            Console.WriteLine("结束执行时间为{0}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            Console.ReadKey();
            #endregion

TaskMethod_2 方法如示例一代码块。方法运行后如下

在这里插入图片描述

4 任务中的异常处理


            #region 3 任务的异常处理
            Task t1 = Task.Factory.StartNew(() => {
                throw new Exception("Task Failed ! ");
            });
            try
            {
                t1.Wait();//当你等待一个任务结束时(通过调用Wait方法或访问其Result属性),所有未处理的异常都会用一个AggregateException对象封装,方便重新抛给调用方。
            }
            catch (AggregateException aex)
            {
                Console.WriteLine(aex.InnerException.Message);  // Task Failed !
            }

            //定义一个父子任务,在父任务与子任务中分别抛出异常
            TaskCreationOptions atp = TaskCreationOptions.AttachedToParent;
            var parent = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("I am Parent");

                Task.Factory.StartNew(() =>   // 子
                {
                    Console.WriteLine("I am Child");
                    throw new Exception("Child Exception");
                }, atp);

                throw new Exception("Parent Exception");

            });

            try {
                parent.Wait();
            }
            catch (AggregateException aex)
            {
                Console.WriteLine(aex.InnerExceptions.Count);  //2 捕获了父异常和子异常
                //这是对于有父子关系的任务,在父任务上等待也会隐式的等待子任务,所有子任务的
                //异常也会传递出来。
            }

            Console.ReadKey();
            #endregion

main 方法块如上,运行结果如下图所示
在这里插入图片描述

当某个任务抛出一个或多个异常时,异常包装在 A g g r e g a t e E x c e p t i o n \textcolor{blue}{AggregateException } AggregateException 异常中。 该异常会传播回与任务联接的线程。 通常,该线程是 等待任务完成 \textcolor{red}{等待任务完成} 等待任务完成的线程
(1)Wait方法
(2)WaitAny方法
(3)WaitAll方法
或访问 Result 属性的线程。AggregateException 通常包含关联任务线程中的所有异常,我们可以在外部通过try catch的方式去处理它,但是这并不意味着并不需要单独处理任务线程的异常,否则可能会因为无解的异常导致程式的中断。

5 取消任务

任务的取消需要用到 C a n c e l l a t i o n T o k e n S o u r c e \textcolor{red}{CancellationTokenSource} CancellationTokenSource C a n c e l l a t i o n T o k e n \textcolor{red}{CancellationToken} CancellationToken类。在取消任务的过程中需要了解一下几点:

  • 可以在构造中传入CancellationToken来构建Task任务,并且CancellationToken可以绑定到多个任务上
  • Task的创建和执行都是独立的,如果在任务执行前取消了任务,那么任务代码将不会执行。如果尝试调用start方法,将会抛出异常InvalidOperationException
  • 任务执行后去执行CancellationTokenSource.Cancel方法,任务不会被取消
  • 需要在任务代码中 显示定义任务中断的逻辑 \textcolor{blue}{显示定义任务中断的逻辑} 显示定义任务中断的逻辑

Main 方法代码块如下


            #region 5 取消任务
            
            Console.OutputEncoding = System.Text.Encoding.UTF8;
            void RunTask(string name) {
                for (int i = 0; i < 10; i++) {
                    Console.WriteLine(name+"运行开始:" + i);
                    Thread.Sleep(1000);
                }
            }

            void RunTaskWithCancellationToken(string name,CancellationToken token) {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine(name + "运行开始:" + i);
                    Thread.Sleep(1000);
                    token.ThrowIfCancellationRequested(); //在运行的Task任务中,显示定义任务取消的逻辑
                }
            }

            CancellationTokenSource source = new CancellationTokenSource();
            CancellationToken cancellation = source.Token;

            //创建一个和CancellationToken关联的任务类
            //t1任务不支持显示取消任务
            Task t1 = Task.Factory.StartNew(() => RunTask("t1"), cancellation);
            //t2任务支持显示取消任务,在任务执行的逻辑中加了ThrowIfCancellationRequested,标识希望在任务中断是抛出异常
            Task t2 = Task.Factory.StartNew(() => RunTaskWithCancellationToken("t2",cancellation), cancellation);
            //t3任务不支持显示取消任务,且需要手动start去运行任务代码。
            Task t3 = new Task(()=>RunTask("t1"),cancellation);
            
            Thread.Sleep(TimeSpan.FromSeconds(3));

            source.Cancel();

            try
            {
                //在一个任务调度前,取消任务,那么将会抛出System.InvalidOperationException 标识在已经完成的工作上呼叫start动作。
                t3.Start();
            }
            catch (Exception e)
            {

                Console.WriteLine("异常类型{0} ,异常消息{1}",e.GetType().Name,e.Message);
            }


            Console.WriteLine("t1 IsCanceled  {0} ,t1 IsCompleted{1}, t1 IsFaulted{2}, t1 status{3}", t1.IsCanceled, t1.IsCompleted, t1.IsFaulted, t1.Status.ToString());
            Console.WriteLine("t2 IsCanceled  {0} ,t2 IsCompleted{1}, t2 IsFaulted{2}, t2 status{3}", t2.IsCanceled, t2.IsCompleted, t2.IsFaulted, t2.Status.ToString());
            Console.WriteLine("t3 IsCanceled  {0} ,t3 IsCompleted{1}, t3 IsFaulted{2}, t3 status{3}", t3.IsCanceled, t3.IsCompleted, t3.IsFaulted, t3.Status.ToString());
            Console.ReadKey();
            #endregion

运行方法后如下
在这里插入图片描述
当主线程不休眠3s (注释掉 Thread.Sleep(TimeSpan.FromSeconds(3)))后,验证结论一
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nginx: mac使用vscode本地调试nginx

vscode安装c语言插件 在extensions中搜索"c/c"&#xff0c; 将前3个插件都安装 在extensions中搜索"cmake"&#xff0c; 将前2个插件都安装 下载nginx源码 nginx 源码: https://github.com/nginx/nginx 编译运行Nginx 修改 /auto/cc/conf 文件&…

【论文阅读】单词级文本攻击TAAD2.2

TAAD2.2论文概览 0.前言1-101.Bridge the Gap Between CV and NLP! A Gradient-based Textual Adversarial Attack Frameworka. 背景b. 方法c. 结果d. 论文及代码 2.TextHacker: Learning based Hybrid Local Search Algorithm for Text Hard-label Adversarial Attacka. 背景b…

数据结构——lesson6二叉树基础

前言 hellohello~这里是土土数据结构学习笔记&#x1f973;&#x1f973; &#x1f4a5;个人主页&#xff1a;大耳朵土土垚的博客 &#x1f4a5; 所属专栏&#xff1a;数据结构学习笔记 &#x1f4a5;对于数据结构顺序表链表有疑问的都可以在上面数据结构的专栏进行学习哦~感…

ping多个IP的工具

Ping Tool 项目地址 python开发的IP搜索小工具 ping一个网段所有IP&#xff0c;显示结果查看某个ip地址开放监听的端口配置可保存

查询IP地址保障电商平台安全

随着电子商务的快速发展&#xff0c;网购已经成为人们日常生活中不可或缺的一部分。然而&#xff0c;网络交易安全一直是人们关注的焦点之一&#xff0c;尤其是在面对日益频发的网络诈骗和欺诈行为时。为了提高网购平台交易的安全性&#xff0c;一种有效的方法是通过查询IP地址…

Java红黑树实现Map简单示例

红黑树&#xff08;Red-Black Tree&#xff09;是一种自平衡的二叉搜索树&#xff0c;它是由 Rudolf Bayer 在 1972 年提出的&#xff0c;后来由 Leo J. Guibas 和 Robert Sedgewick 在 1978 年发表的论文中形式化定义。 红黑树具有以下特性&#xff1a; 1.节点颜色&#xff1…

java数据结构与算法刷题-----LeetCode653. 两数之和 IV - 输入二叉搜索树

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 前序遍历加hash2. 中序遍历双指针 1. 前序遍历加hash 解题思…

(上海电力展)2024上海国际智慧电力与电气设备展览会

2024上海国际智慧电力与电气设备展览会 2024 Shanghai International Intelligent Power and Electrical Equipment Exhibition 时 间&#xff1a;2024年7月13-15日 地 点&#xff1a;上海新国际博览中心 展会简介Introduction 随着全球进入互联网和数字经济时…

逻辑分析仪分析硬件spi

一&#xff0c;cubemx配置好后&#xff0c;使用spi接口发送数据&#xff1b; 发送5个字节&#xff1a;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff1b; 第1个字节&#xff1a;1&#xff1b; 第2个字节&#xff1a;2&#xff1b; 第3个字节&#xff1a;3&am…

【趣玩一下】StreamDiffusion一秒100张!实时生成二次元老婆照!

源代码 https://github.com/cumulo-autumn/StreamDiffusion 基础原理 首先Stream Batch&#xff0c;是将原来顺序的去噪步骤改为批量化处理。允许在一个批处理中&#xff0c;每幅图像处于去噪流程的不同阶段。 如此一来&#xff0c;可以大大减少UNet推理次数&#xff0c;显著…

基于单片机的医院输液系统设计

目 录 摘 要 Ⅰ Abstract Ⅱ 引 言 1 1系统方案设计与论证 3 1.1系统硬件结构总体设计方案 3 1.2点滴速度测量电路方案的选择与论证 3 1.3液面检测电路方案的选择与论证 4 1.4通过电机控制滴速电路的方案与论证 4 1.5显示器接口电路方案选择与论证 5 1.6键盘接口电路方案选择与…

十:套接字和标准I/O,以及分离I/O流

1 标准I/O函数的优点 C语言标准IO整理 1.1 标准I/O函数的两个优点 标准I/O函数具有良好的移植性。 标准I/O函数可以利用缓冲提高性能 从图中可以看出&#xff0c;使用标准I/O函数传输数据时&#xff0c;经过两个缓冲。例如&#xff0c;使用fputs函数传输字符串 “Hello” 时…

数据分析-Pandas数据的画图设置

数据分析-Pandas数据的画图设置 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表&#x…

TypeScript(四)枚举类型(Enum Types),类型别名(Type),运算符

一、枚举类型 使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript支持数字的和基于字符串的枚举。 枚举类型的特点 可以给一组数值取上一个更好理解的名字&#xff1b;一个枚举中只会存在几个固定的值&#xff0c;并不会…

C语言第三十六弹---文件操作(中)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 文件操作 1、文件的顺序读写 1.1、顺序读写函数介绍 1.1.1、fgetc 与 fputc 1.1.2、fgets 与 fputs 1.1.3、fscanf 与 fprintf 1.1.4、fread 与 fwrite 1.…

【vue3之组合式API及其新特性】

组合式API及其新特性 一、setup1.写法2.如何访问3.语法糖4.同步返回对象 二、reactive()和ref()1.reactive()2.ref() 三、computed四、watch函数1.侦听单个数据2.侦听多个数据3. immediate4. deep5.精确侦听对象的某个属性 五、生命周期函数六、组件通信1.父传子2. 子传父 七、…

云消息队列 Confluent 版正式上线!

作者&#xff1a;阿里云消息队列 前言 在 2023 年杭州云栖大会上&#xff0c;Confluent 成为阿里云技术合作伙伴&#xff0c;在此基础上&#xff0c;双方展开了深度合作&#xff0c;并在今天&#xff08;3月1日&#xff09;正式上线“云消息队列 Confluent 版”。 通过将 Co…

基于51单片机心率脉搏计设计

目 录 摘 要 I Abstract II 引 言 1 1 控制系统设计 3 1.1 系统方案设计 3 1.2 系统总体设计 4 2 硬件设计 5 2.1 主控电路 5 2.2 驱动电路 8 2.3 信号采集电路 10 2.4 显示电路 13 2.5 总体电路图设计 15 3 软件设计 16 3.1 软件开发环境的介绍 16 3.2 系统重要函数介绍 16 4…

解决QMYSQL driver not loaded问题

前言 之前都是在Qt5.51上开发&#xff0c;连接mysql数据库一直没有问题&#xff0c;换到5.15.2后一直报错 一查才发现\5.15.2\msvc2019_64\plugins\sqldrivers目录下没有qsqlmysql了&#xff0c;5.5.1是有的&#xff0c;5.15.2是要自己编译的。。。 下载源码 安装qt的时候没…

大型多模态智能体:综述

论文链接&#xff1a;https://arxiv.org/abs/2402.15116 大型语言模型&#xff08;LLMs&#xff09;在推动文本基础的智能体方面已经取得了超群的性能&#xff0c;赋予它们类似人类的决策和推理能力。与此同时&#xff0c;一个新兴的研究趋势集中于将这些LLM驱动的智能体扩展到…