1、知识回顾,简要概述

前面两篇关于Task的随笔,C# 多线程五之Task(任务)一 和 C# 多线程六之Task(任务)二,介绍了关于Task的一些基本的用法,以及一些使用的要点,如果都看懂了,本文将介绍另一个Task的特殊用法,前面介绍了,如何通过一个父任务创建多个子任务,且这些子任务都必须要支持取消的例子,常规做法是,通过new 一个Task数组对象,然后在该对象的内部创建多个Task任务,然后给这些任务指定TaskCreationOptions.AttachedToParent,这样所有的子任务都关联到了父任务,接着给这些子任务,绑定一个CancellationToken类实例,当其中一个子任务发生异常时,调用CancellationToken类实例的Cancel方法,将其余的子任务全都取消,大致代码如下:

        static void Main(string[] args){var parentTask = new Task<int[]>(() =>{var results = new int[3];var cancelTokenSource = new CancellationTokenSource();var childTasks = new Task[] {new Task(() => results[0] = ChildThreadOne(cancelTokenSource.Token),cancelTokenSource.Token, TaskCreationOptions.AttachedToParent),new Task(() => results[1] = ChildThreadTwo(cancelTokenSource.Token),cancelTokenSource.Token, TaskCreationOptions.AttachedToParent),new Task(() => results[2] = ChildThreadThree(cancelTokenSource.Token),cancelTokenSource.Token, TaskCreationOptions.AttachedToParent),};//开启所有的子任务childTasks.ForEach(f => { f.Start(); });//如果有子任务发生异常,那么通过取消信号量终止所有的任务childTasks.ForEach(f =>{f.ContinueWith(task=> cancelTokenSource.Cancel(), TaskContinuationOptions.OnlyOnFaulted);});return results;});parentTask.Start();parentTask.ContinueWith(x =>{Console.WriteLine("当父任务执行完毕时,CLR会唤起一个新线程,将父任务的返回值(子任务的返回值)输出,所以这里不会有任何的线程发生阻塞");foreach (var re in parentTask.Result){Console.WriteLine("子任务的返回值分别为:{0}", re);}});Console.WriteLine("主线程不会阻塞,它会继续执行");Console.ReadKey();//必须加这行代码,因为Task时线程池线程,属于后台线程
        }/// <summary>/// 子任务一/// </summary>static int ChildThreadOne(CancellationToken token){Thread.Sleep(3000);//模拟长时间计算操作
            token.ThrowIfCancellationRequested();Console.WriteLine("子任务一完成了计算任务,并返回值:{0}", 6);return 6;}/// <summary>/// 子任务二/// </summary>static int ChildThreadTwo(CancellationToken token){Thread.Sleep(2000);//模拟长时间计算操作
            token.ThrowIfCancellationRequested();throw new Exception("模拟抛出异常");}/// <summary>/// 子任务三/// </summary>static int ChildThreadThree(CancellationToken token){Thread.Sleep(3000);//模拟长时间计算操作
            token.ThrowIfCancellationRequested();Console.WriteLine("子任务三完成了计算任务,并返回值:{0}", 6);return 6;}}/// <summary>/// Linq扩展/// </summary>public static class LinqExtension{public static void ForEach<T>(this IEnumerable<T> enumerators, Action<T> action){foreach (var item in enumerators){action(item);}}}

这里需要注意,这里给父任务parentTask开启了三个子任务,并且通过TaskCreationOptions.AttachedToParent指定了所有的子任务不能独立于父任务运行,并且给所有的子任务,传递了CancellationToken信号量,当其中一个子任务发生异常时,所有其余的子任务都终止,但是你必须知道的是,你没有判断哪个任务会被终止,因为如果不指定线程优先级,哪怕制定了优先级,你也无法确定的判断某个计算任务在什么时候会调度完,所以我給正常的执行的任务,Sleep了三秒,抛出异常的任务Sleep了两秒,所以所有的子线程都无法执行完毕.

2、代码重构

ok,虽然上面的代码很好的完成了我们在代码层面的需求,但是处于对代码的重用性考虑,有没有发现这个问题:

这块操作,可以重构的,因为所有的参数都一样,当然你可以去抽象一个共有的方法,里面放一个Func委托,当然把参数抽象出来,形成一个公共的方法,像下面这样做:

    class Program{private static CancellationTokenSource cancelTokenSource = new CancellationTokenSource();private static TaskCreationOptions taskCreationOptions = TaskCreationOptions.AttachedToParent;static void Main(string[] args){var parentTask = new Task<int[]>(() =>{var results = new int[3];var childTasks = new Task[] {ExecuteChildThread(task=> results[0]=ChildThreadOne(cancelTokenSource.Token)),ExecuteChildThread(task=> results[1]=ChildThreadTwo(cancelTokenSource.Token)),ExecuteChildThread(task=> results[2]=ChildThreadThree(cancelTokenSource.Token))};//开启所有的子任务childTasks.ForEach(f => { f.Start(); });//如果有子任务发生异常,那么通过取消信号量终止所有的任务childTasks.ForEach(f =>{f.ContinueWith(task=> cancelTokenSource.Cancel(), TaskContinuationOptions.OnlyOnFaulted);});return results;});parentTask.Start();parentTask.ContinueWith(x =>{Console.WriteLine("当父任务执行完毕时,CLR会唤起一个新线程,将父任务的返回值(子任务的返回值)输出,所以这里不会有任何的线程发生阻塞");foreach (var re in parentTask.Result){Console.WriteLine("子任务的返回值分别为:{0}", re);}});Console.WriteLine("主线程不会阻塞,它会继续执行");Console.ReadKey();//必须加这行代码,因为Task时线程池线程,属于后台线程
        }/// <summary>/// 子任务一/// </summary>static int ChildThreadOne(CancellationToken token){Thread.Sleep(2000);//模拟长时间计算操作
            token.ThrowIfCancellationRequested();Console.WriteLine("子任务一完成了计算任务,并返回值:{0}", 6);return 6;}/// <summary>/// 子任务二/// </summary>static int ChildThreadTwo(CancellationToken token){Thread.Sleep(2000);//模拟长时间计算操作
            token.ThrowIfCancellationRequested();Console.WriteLine("子任务二完成了计算任务,并返回值:{0}", 6);return 6;}/// <summary>/// 子任务三/// </summary>static int ChildThreadThree(CancellationToken token){Thread.Sleep(2000);//模拟长时间计算操作
            token.ThrowIfCancellationRequested();Console.WriteLine("子任务三完成了计算任务,并返回值:{0}", 6);return 6;}/// <summary>/// 创建一个通用的子线程方法,里面封装了所有子线程的需要设置的公共参数/// </summary>/// <param name="func"></param>/// <returns></returns>static Task<int> ExecuteChildThread(Func<CancellationToken, int> func){var t=new Task<int>(()=>func.Invoke(cancelTokenSource.Token), cancelTokenSource.Token, taskCreationOptions);return t;}}/// <summary>/// Linq扩展/// </summary>public static class LinqExtension{public static void ForEach<T>(this IEnumerable<T> enumerators, Action<T> action){foreach (var item in enumerators){action(item);}}}

ok,通过对子任务的抽象,你可以这么干,但是MS提供了更好的办法,你又何必重复造轮子呢?而且这里存在着潜在的多线程争用问题,

所有的线程都用到了这两个全局变量,最好加个锁,但是加了锁之后,性能就会受到影响.

但是奇怪的是,我无法重现,如果你能重现那是最好的,下面就开始介绍Ms提供的任务工厂

3、任务工厂实战

下面再次对上面的方法进行重构,用任务工厂的方式,首先使用TaskFactory任务工厂的前提你必须清楚,就是创建的子任务,必须是一组共享配置的子任务对象集,所以,如果当中如果某个子任务需要使用特殊的配置,那就不能使用任务工厂,也不是不能使用,就是那个子任务你必须独立出来,不能放到任务工厂里面.ok,了解了前提条件后,开始实践,代码如下:

    class Program{static void Main(string[] args){var parentTask=Task.Run(()=> {var cts = new CancellationTokenSource();//通过TaskFactory设置子任务的公共参数var tf = new TaskFactory<int>(cts.Token,TaskCreationOptions.AttachedToParent,TaskContinuationOptions.ExecuteSynchronously,TaskScheduler.Default);//通过TaskFactory设置所有的子任务,这些子任务共享上面公共参数var childTasks = new Task<int>[] {tf.StartNew(() => ChildThreadOne(cts.Token)),tf.StartNew(() => ChildThreadTwo(cts.Token)),tf.StartNew(() => ChildThreadThree(cts.Token))};//如果子任务发生异常,则向余下没有执行完毕的子任务传递取消执行的信号,如果有子任务执行完毕了,那就没有办法了childTasks.ForEach(f =>{f.ContinueWith(childTask => cts.Cancel(), TaskContinuationOptions.OnlyOnFaulted);});//遍历所有通过TaskFactory创建的子任务,然后筛选出没有被取消和没有发生异常的子任务,或者这些任务中的最大返回值//这个任务不阻塞线程,只有当所有的子任务执行完毕之后,CLR会唤起线程池中的一个新线程来执行这个操作//通过给唤起子线程设置CancellationToken.None,来达到这个线程不会被任何因素来取消该线程的目的var tfTask = tf.ContinueWhenAll(childTasks,completedTasks => completedTasks.Where(completedTask => !completedTask.IsCanceled && !completedTask.IsFaulted).Max(completedTask => completedTask.Result), CancellationToken.None);//输出所有符合要求的子任务集合的返回值集合中的最大值,并指定该任务,在tfTask任务的基础上同步执行的效果通过TaskContinuationOptions.ExecuteSynchronouslytfTask.ContinueWith(childTasksCompleteTask =>{Console.WriteLine("The Max Return Value is {0}", childTasksCompleteTask.Result);},TaskContinuationOptions.ExecuteSynchronously);});Console.WriteLine("主线程继续做它的事情");Console.ReadKey();//必须加这行代码,因为Task时线程池线程,属于后台线程
        }/// <summary>/// 子任务一/// </summary>static int ChildThreadOne(CancellationToken token){var returnValue = 6;Thread.Sleep(3000);//模拟长时间计算操作
            token.ThrowIfCancellationRequested();Console.WriteLine("子任务一完成了计算任务,并返回值:{0}", returnValue);return returnValue;}/// <summary>/// 子任务二/// </summary>static int ChildThreadTwo(CancellationToken token){var returnValue = 66;Thread.Sleep(3000);//模拟长时间计算操作
            token.ThrowIfCancellationRequested();Console.WriteLine("子任务二完成了计算任务,并返回值:{0}", returnValue);return returnValue;}/// <summary>/// 子任务三/// </summary>static int ChildThreadThree(CancellationToken token){Thread.Sleep(2000);//模拟长时间计算操作throw new Exception("模拟抛出异常");}}/// <summary>/// Linq扩展/// </summary>public static class LinqExtension{public static void ForEach<T>(this IEnumerable<T> enumerators, Action<T> action){foreach (var item in enumerators){action(item);}}}

因为我给异常线程设置了2秒的休眠时间,正常子线程设置了3秒的休眠时间,所以所有的线程都没有执行完毕,就被取消掉了.如果修改下正常线程的休眠时间为1秒,将会得到以下的输出:

so,TaskFactory完美的完成了它的任务,且不会有任务线程发生阻塞的情况。

4、如何解决任务工厂抛出的异常

我发现一个很奇怪的问题,就是当当外部通过一个Task.Run创建的父任务,无法获取TaskFactory下子任务集群抛出的异常,代码如下:

    class Program{static void Main(string[] args){var pTask = Task.Run(() =>{var cts = new CancellationTokenSource();//通过TaskFactory设置子任务的公共参数var tf = new TaskFactory<int>(cts.Token, TaskCreationOptions.AttachedToParent, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);//通过TaskFactory设置所有的子任务,这些子任务共享上面公共参数var childTasks = new Task<int>[] {tf.StartNew(() => ChildThreadOne(cts.Token)),tf.StartNew(() => ChildThreadTwo(cts.Token)),tf.StartNew(() => ChildThreadThree(cts.Token))};});pTask.ContinueWith(tasks =>{var exceptions = tasks.Exception;foreach (var ex in exceptions.InnerExceptions){Console.WriteLine(ex);}},TaskContinuationOptions.OnlyOnFaulted);Console.WriteLine("主线程继续做它的事情");Console.ReadKey();//必须加这行代码,因为Task时线程池线程,属于后台线程
        }/// <summary>/// 子任务一/// </summary>static int ChildThreadOne(CancellationToken token){var returnValue = 6;Thread.Sleep(3000);//模拟长时间计算操作
            token.ThrowIfCancellationRequested();Console.WriteLine("子任务一完成了计算任务,并返回值:{0}", returnValue);return returnValue;}/// <summary>/// 子任务二/// </summary>static int ChildThreadTwo(CancellationToken token){var returnValue = 66;Thread.Sleep(3000);//模拟长时间计算操作
            token.ThrowIfCancellationRequested();Console.WriteLine("子任务二完成了计算任务,并返回值:{0}", returnValue);return returnValue;}/// <summary>/// 子任务三/// </summary>static int ChildThreadThree(CancellationToken token){Thread.Sleep(2000);//模拟长时间计算操作throw new Exception("模拟抛出异常");}}/// <summary>/// Linq扩展/// </summary>public static class LinqExtension{public static void ForEach<T>(this IEnumerable<T> enumerators, Action<T> action){foreach (var item in enumerators){action(item);}}}

很其怪,不过这说明,外部的父任务,无法和TaskFactory建立关联,如果你们能找到方法,欢迎在下面评论区评论,因为这个所以,要处理子任务抛出的异常.只能通过过滤异常子任务,然后在子任务里单独记录日志的方式,去处理:

暂时没有想到更好的办法.

转载于:https://www.cnblogs.com/GreenLeaves/p/10088635.html

C# 多线程六之Task(任务)三之任务工厂相关推荐

  1. Debezium报错处理系列之三十六:Task threw an uncaught and unrecoverable exception. Task is being killed and will

    Debezium报错处理系列之三十六:Task threw an uncaught and unrecoverable exception. Task is being killed and will ...

  2. 四五六年级计算机教学计划,三至六年级信息技术教学计划

    <三至六年级信息技术教学计划>由会员分享,可在线阅读,更多相关<三至六年级信息技术教学计划(4页珍藏版)>请在人人文库网上搜索. 1.三至六年级信息技术教学计划 三至六年级信息 ...

  3. 如何建立24位位图文件_NBA十大最伟大球衣号码 24号第六 23号第三 第一无悬念(106)...

    NBA十大最伟大球衣号码 24号第六 23号第三 第一无悬念 来说说联盟历史上最伟大的10个球衣号码,其评选依据在于球员在穿着这件球衣号码时所取得的个人荣誉,球星数目,和在场上的表现等多个因素所得出的 ...

  4. STC8H开发(六): SPI驱动ADXL345三轴加速度检测模块

    目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...

  5. java 工厂模式实现_Java三种实现工厂模式的方法

    学习工厂模式的时候就曾思考过这么写的好处,再手动敲了代码后发现自己更糊涂了,后来搜索例子和各种文案才有点概念,特此记录一下个人的理解 工厂模式的好处: 1.减少了重复代码 2.让创建对象于使用方法分离 ...

  6. 设计模式学习笔记(三)简单工厂、工厂方法和抽象工厂之间的区别

    设计模式中的工厂模式(Factory Design pattern)是一个比较常用的创建型设计模式,其中可以细分为三种:简单工厂(Simple Factory).工厂方法(Factory Method ...

  7. 细说多线程(六) —— 异步 SqlCommand

    目录 一.线程的定义 二.线程的基础知识 三.以ThreadStart方式实现多线程 四.CLR线程池的工作者线程 五.CLR线程池的I/O线程 六.异步 SqlCommand 七.并行编程与PLIN ...

  8. python多线程并发编程技术_三 python并发编程之多线程-理论

    一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程 车间负责把资源整合 ...

  9. 2020PMP(第六版)每日三题

    PMP12月份考试已经顺利通过,这里将平时积累的每日三题发出来供大家分享:(结尾分享一点考试心得体会) 一.题目部分: 2020.9.27 三题: 1.项目是: A. 一组持续的活动中执行的一个过程或 ...

最新文章

  1. vtk相机_C#开发PACS医学影像三维重建(一)使用VTK重建3D影像
  2. 《视频直播技术详解》系列之八:直播云 SDK 性能测试模型
  3. 使用imbalanced-learn处理数据不均衡问题
  4. kangle支不支持PHP_【转载】PHP调用kangle的API
  5. 微信H5页面内实现一键关注公众号
  6. 最常用的css垂直居中方法
  7. 白皮 Chapter 1
  8. java severlet 例子_Java开发Servlet实例
  9. 详解Python函数如何重载?
  10. matlab程序阻尼牛顿法,matlab阻尼牛顿法
  11. 猎头公司人才管理现状及人才资源管理解决方案
  12. C# 判断有向图是否存在环
  13. 如何获得当前所在的DLL模块名称
  14. 头歌--人脸识别系统--Face recognition 人脸识别
  15. C语言经典例题-将输入的两位数转成英文
  16. 苹果电脑mac系统运行卡顿 反应慢怎么办?
  17. Detachment HDU - 5976(数学+费马小定理求逆元+前缀和前缀积)
  18. 老调重弹:JDBC系列 之 JDBC层次结构和基本构成
  19. 腾讯云认证体系TCA、TCP和TCE认证考试攻略与常见问题
  20. 结构化方法和面向对象方法详解

热门文章

  1. 人的一生应当这样度过
  2. 121.应用层有什么协议,作用是什么?
  3. 炉石传说源代码_python抓取4399上的炉石传说原画,几百张原画拼接成女神画像!...
  4. 051_Array对象
  5. 058_JavaScript函数arguments对象
  6. 016_html段落
  7. java.lang包怎么用_java.lang.io包的使用
  8. python算不算编程_Python 并不适合职场编程
  9. ssm框架app管理平台_后端程序员跨平台应用的前端框架uni-app初探
  10. java实现单向链表