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[];
var cancelTokenSource = new CancellationTokenSource();
var childTasks = new Task[] {
new Task(() => results[] = ChildThreadOne(cancelTokenSource.Token),cancelTokenSource.Token, TaskCreationOptions.AttachedToParent),
new Task(() => results[] = ChildThreadTwo(cancelTokenSource.Token),cancelTokenSource.Token, TaskCreationOptions.AttachedToParent),
new Task(() => results[] = 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();//模拟长时间计算操作
token.ThrowIfCancellationRequested();
Console.WriteLine("子任务一完成了计算任务,并返回值:{0}", );
return ;
} /// <summary>
/// 子任务二
/// </summary>
static int ChildThreadTwo(CancellationToken token)
{
Thread.Sleep();//模拟长时间计算操作
token.ThrowIfCancellationRequested();
throw new Exception("模拟抛出异常");
} /// <summary>
/// 子任务三
/// </summary>
static int ChildThreadThree(CancellationToken token)
{
Thread.Sleep();//模拟长时间计算操作
token.ThrowIfCancellationRequested();
Console.WriteLine("子任务三完成了计算任务,并返回值:{0}", );
return ;
}
} /// <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[]; var childTasks = new Task[] {
ExecuteChildThread(task=> results[]=ChildThreadOne(cancelTokenSource.Token)),
ExecuteChildThread(task=> results[]=ChildThreadTwo(cancelTokenSource.Token)),
ExecuteChildThread(task=> results[]=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();//模拟长时间计算操作
token.ThrowIfCancellationRequested();
Console.WriteLine("子任务一完成了计算任务,并返回值:{0}", );
return ;
} /// <summary>
/// 子任务二
/// </summary>
static int ChildThreadTwo(CancellationToken token)
{
Thread.Sleep();//模拟长时间计算操作
token.ThrowIfCancellationRequested();
Console.WriteLine("子任务二完成了计算任务,并返回值:{0}", );
return ;
} /// <summary>
/// 子任务三
/// </summary>
static int ChildThreadThree(CancellationToken token)
{
Thread.Sleep();//模拟长时间计算操作
token.ThrowIfCancellationRequested();
Console.WriteLine("子任务三完成了计算任务,并返回值:{0}", );
return ;
} /// <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.ExecuteSynchronously
tfTask.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 = ;
Thread.Sleep();//模拟长时间计算操作
token.ThrowIfCancellationRequested();
Console.WriteLine("子任务一完成了计算任务,并返回值:{0}", returnValue);
return returnValue;
} /// <summary>
/// 子任务二
/// </summary>
static int ChildThreadTwo(CancellationToken token)
{
var returnValue = ;
Thread.Sleep();//模拟长时间计算操作
token.ThrowIfCancellationRequested();
Console.WriteLine("子任务二完成了计算任务,并返回值:{0}", returnValue);
return returnValue;
} /// <summary>
/// 子任务三
/// </summary>
static int ChildThreadThree(CancellationToken token)
{
Thread.Sleep();//模拟长时间计算操作
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 = ;
Thread.Sleep();//模拟长时间计算操作
token.ThrowIfCancellationRequested();
Console.WriteLine("子任务一完成了计算任务,并返回值:{0}", returnValue);
return returnValue;
} /// <summary>
/// 子任务二
/// </summary>
static int ChildThreadTwo(CancellationToken token)
{
var returnValue = ;
Thread.Sleep();//模拟长时间计算操作
token.ThrowIfCancellationRequested();
Console.WriteLine("子任务二完成了计算任务,并返回值:{0}", returnValue);
return returnValue;
} /// <summary>
/// 子任务三
/// </summary>
static int ChildThreadThree(CancellationToken token)
{
Thread.Sleep();//模拟长时间计算操作
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建立关联,如果你们能找到方法,欢迎在下面评论区评论,因为这个所以,要处理子任务抛出的异常.只能通过过滤异常子任务,然后在子任务里单独记录日志的方式,去处理:

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

C# 多线程六之Task(任务)三之任务工厂的更多相关文章

  1. C# 多线程六之Task(任务)二

    前面介绍了Task的由来,以及简单的使用,包括开启任务,处理任务的超时.异常.取消.以及如果获取任务的返回值,在回去返回值之后,立即唤起新的线程处理返回值.且如果前面的任务发生异常,唤起任务如果有效的 ...

  2. 重新想象 Windows 8 Store Apps (43) - 多线程之任务: Task 基础, 多任务并行执行, 并行运算(Parallel)

    [源码下载] 重新想象 Windows 8 Store Apps (43) - 多线程之任务: Task 基础, 多任务并行执行, 并行运算(Parallel) 作者:webabcd 介绍重新想象 W ...

  3. c#中@标志的作用 C#通过序列化实现深表复制 细说并发编程-TPL 大数据量下DataTable To List效率对比 【转载】C#工具类:实现文件操作File的工具类 异步多线程 Async .net 多线程 Thread ThreadPool Task .Net 反射学习

    c#中@标志的作用   参考微软官方文档-特殊字符@,地址 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/toke ...

  4. 异步多线程 Thread ThreadPool Task

    一.线程 Thread ThreadPool 线程是Windows任务调度的最小单位,线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针.程序计数器等),但代码区是共享的,即不同的线程可以 ...

  5. IOS 多线程,线程同步的三种方式

    本文主要是讲述 IOS 多线程,线程同步的三种方式,更多IOS技术知识,请登陆疯狂软件教育官网. 一般情况下我们使用线程,在多个线程共同访问同一块资源.为保护线程资源的安全和线程访问的正确性. 在IO ...

  6. C#多线程实现方法——Task/Task.Factary

    原文:C#多线程实现方法--Task/Task.Factary Task 使用 Task以及Task.Factory都是在.Net 4引用的.Task跟Thread很类似,通过下面例子可以看到. st ...

  7. .net 多线程 Thread ThreadPool Task

    先准备一个耗时方法 /// <summary>/// 耗时方法/// </summary>/// <param name="name">< ...

  8. 转载 .Net多线程编程—任务Task https://www.cnblogs.com/hdwgxz/p/6258014.html

    .Net多线程编程—任务Task   1 System.Threading.Tasks.Task简介 一个Task表示一个异步操作,Task的创建和执行是独立的. 只读属性: 返回值 名称 说明 ob ...

  9. App架构师实践指南六之性能优化三

    App架构师实践指南六之性能优化三 2018年08月02日 13:57:57 nicolelili1 阅读数:190   内存性能优化1.内存机制和原理 1.1 内存管理内存时一个基础又高深的话题,从 ...

随机推荐

  1. jq页面加载分割截图

    <script> $(document).ready(function() { if (!Array.prototype.forEach) { Array.prototype.forEac ...

  2. Spring boot 集成Dubbo简单版,准备工作,

    一.GitHub上找寻Dubbo资源 阿里巴巴在其GitHub上已经写好一个Github案例所以我们只要进入其Git上就可以看到和clone这个项目 二.阿里巴巴GitHub使用 https://gi ...

  3. POJ 2433 Landscaping (贪心)

    题意:给定一个序列表示一群山,要你保留最多 K 个山峰,最少要削去多少体积和土.一个山峰是指一段连续的相等的区间,并且左边和右边只能比这个区间低,或者是边界. 析:贪心,每次都寻找体积最小的山峰,然后 ...

  4. ubuntu 设置DNS

    sudo vi /etc/resolv.conf #加入nameserver 114.114.114.114

  5. hadoop集群的三种运行模式

    单机(本地)模式: 这种模式在一台单机上运行,没有分布式文件系统,而是直接读写本地操作系统的文件系统.在单机模式(standalone)中不会存在守护进程,所有东西都运行在一个JVM上.这里同样没有D ...

  6. DDR中常用概念

    一.DDR中的基本术语 (2)逻辑Bank SDRAM的内部是一个存储阵列,类似于一张表格.和表格的检索原理一样,先指定一个行(Row),再指定一个列(Column),就可以准确地找到所需要的单元格, ...

  7. Word图片上传控件-eWebEditor9x整合教程-Xproer.WordPaster

    示例下载(JSP):eWebEditor9x, 示例下载(.NET):eWebEditor9x,   1.1. 集成到eWebEditor9x 主要步骤如下: 1.增加WordPaster文件夹   ...

  8. 文档/视图(01):第一个Demo

    学习文档视图编程的第一个demo,程序比较简单,主要对文档模板,文档,视图等相互关系的一个了解. 功能:菜单添加一个[操作]项,然后新建四份空白文档,点击[操作]之后,在四份空白文档上面各绘制一个Bu ...

  9. 记录:CSS特殊性——权值规则

    浏览器是根据权值来判断使用哪种css样式的,权值高的就使用哪种css样式. 下面是权值的规则: 标签的权值为1,类选择符的权值为10,ID选择符的权值最高为100.例如下面的代码: p{color:r ...

  10. (转)MVC一个页面多个submit

    转自:http://stackoverflow.com/questions/442704/how-do-you-handle-multiple-submit-buttons-in-asp-net-mv ...