线程阶段性总结——APM,ThreadPool,Task,TaskScheduler ,CancellationTokenSource
不管我们使用thread,threadPool,task,还是APM异步,本质都是在使用多线程。对于新手来说,不太敢用多线程的原因,就我个人的体验来说,就是对多线程的异常捕获方式或时机缺乏了解,而一旦出现异常没有捕获,将会带来难以发现的bug,进而造成系统崩溃。而多线程本身也不是一朝一夕就能学好的,必须不断的去学习总结,所以我个人认为你要用一种线程模型,首先要对它有足够的了解,特别是对异常的捕获。如果你没有完全的把握,最好在实际开发中谨慎的用多线程。
1,APM异步编程模型。
采用BeginXXX和EndXXX方法。关于异常的捕捉,对于刚调用BeginXXX抛出的异常,异步操作可能还没有进入队列。这种异常一般可以忽略。对于进入异步操作时发生的异常,会将错误码放入IAsyncResult对象中,在我们调用EndXXX方法时,会将这个错误码转换成一个恰当的Exception再次抛出。所以对于APM编程模型来说,我们只用对EndXXX方法进行异常捕捉。伪代码:
Try
{
Result = someObj.EndXXX(IAsyncResult);
}
Catch(xxxException e)
{
//异常处理
}
注意事项:
1) 对于EndXXX方法的调用是必须的,否则可能会造成资源的泄漏,即使你可能不关心异步调用的返回结果,也要记住调用这个方法。
2) 只能调用一次EndXXX方法。
3) 调用EndXXX方法总是使用和BeginXXX时相同的对象。这里辨别的是引用,引用不同就被视为不同的对象。对于Delegate要补充一点,即使是相同签名的委托,它们被编译器编译成具体的类,这些类的类名是不一样的。
4) 不能取消异步I/O限制的异步操作。不要迷信这句话,他说的是I/O操作,是指的一个请求动作,如果我们的是多次请求,比如异步分块上传文件,是可以做取消功能的。
5) FCL中有许多的I/O操作类都实现了APM。如派生自System.IO.Stream的类,Socket,Dns,WebRequest,还有SqlCommand等等。它们都提供了BeginXXX和EndXXX方法。
6) 可以用APM来执行任何方法,我们只需要定义一个与方法签名一致的delegate,delegate编译后会生成一个BeginInvoke和EndInvoke方法来支持APM操作。
2,Thread & ThreadPool
Thread和ThreadPool发起异步的缺点:
1)没有内建的机制知道任务何时完成。
2)没法得到任务的返回值。
Thread的开销太大,尽量用ThreadPool,除非你要显示指定你的thread为前台线程或要对线程设置优先级,否则就不要用thread。
注意:线程池是由所有的AppDomain共享的。一个CLR维持一个线程池。
3,Task
Task的引入,解决了上面的两个问题。
1)Task可通过Wait()方法来等待任务的完成。这个方法是阻塞的。
2)通过Task.Result可以得到返回结果。在Result内部调用了Wait方法,所以查询这个属性是阻塞的。
3)对于任务函数的未处理异常,会被包装成AggregateException异常抛出。可捕捉Wait()方法和Result属性。通过AggregateException的InnerExceptions可以进一步查询具体的异常。
4)Task的静态方法WaitAny和WaitAll可以等待多个任务返回。同样可以捕捉这两个方法的异常。
5)对于没有调用Wait,Result,Exception来查询未处理异常的情况,例如:只调用了Task.Start方法。Task对象被回收时,Finalize方法会再次抛出这个异常终止进程。可以向TaskScheduler.UnobservedTaskException事件注册一个方法,来处理这类异常。通过UnobservedTaskExceptionEventArgs的SetObserved方法,可以忽略掉这个异常,使进程不会终止。
6)构造Task时,可以传递CancellationToken对象,以支持取消。如果是任务函数,通过调用CancellationToken.ThrowIfCancellationRequested 抛出的异常,类型是OperationCanceledException。如果任务函数没有传递CancellationToken对象,那么抛出的异常是TaskCanceledException,相当于任务级别的取消。
7) Task的ContinueWith方法可以在第一个任务完成时开启第二个任务。这个功能很强大,ContinueWith方法并不阻塞调用线程,它是异步的。我们可以在ContinueWith中写一个事件回调方法,它可以起到事件完成通知的作用。但它只能收到任务完成的通知,要实现任务进度的更新通知,到目前为止,task依然做不到。
Task<int> t = new Task<int>(() => Sum(100)); t.Start();
t.ContinueWith(task => Console.WriteLine("result:" + task.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
t.ContinueWith(task => Console.WriteLine("canceled"), TaskContinuationOptions.OnlyOnCanceled);
t.ContinueWith(task => Console.WriteLine("failed"), TaskContinuationOptions.OnlyOnFaulted);
对于上面的这一窜代码,如果有未处理异常,同样会造成进程终止。你同样可以用TaskScheduler.UnobservedTaskException事件注册一个方法来处理。
8)Task可以指定子任务,子任务没有完成,父任务的ContinueTask也不会执行。关于异常和上面的处理方法一样。因为这个也是不阻塞的,未处理异常暂时也只能在TaskScheduler.UnobservedTaskException里处理。
Task<Int32[]> parent = new Task<int[]>(() =>
{
Int32[] result = new Int32[3];
new Task<Int32>(() => result[0] = Sum(100), TaskCreationOptions.AttachedToParent).Start();
new Task<Int32>(() => result[1] = Sum(200), TaskCreationOptions.AttachedToParent).Start();
new Task<Int32>(() => result[2] = Sum(300), TaskCreationOptions.AttachedToParent).Start(); return result;
}); parent.ContinueWith(parentTask => Array.ForEach(parentTask.Result, num => Console.WriteLine(num))); parent.Start();
9)TaskFactroy可以简化一组相似Task的创建工作。
Task parent = new Task(() =>
{
CancellationTokenSource cts = new CancellationTokenSource(); TaskFactory<Int32> tf = new TaskFactory<Int32>(cts.Token, TaskCreationOptions.AttachedToParent,
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); //create three child task
var childTasks = new[]{
tf.StartNew(()=>Sum(cts.Token,100)),
tf.StartNew(()=>Sum(cts.Token,200)),
tf.StartNew(()=>Sum(cts.Token,Int32.MaxValue))
}; //when one failed,cancel the other
for (int i = 0; i < childTasks.Length; i++)
{
childTasks[i].ContinueWith(task => cts.Cancel(), TaskContinuationOptions.OnlyOnFaulted);
} //display the maxvalue
tf.ContinueWhenAll(
childTasks, completeTasks => completeTasks.Where(
task => !task.IsCanceled && !task.IsFaulted).Max(t => t.Result),
CancellationToken.None).ContinueWith(task => Console.WriteLine("the max is:" + task.Result)); }); //show exception
parent.ContinueWith(p =>
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("error occours:");
foreach (var e in p.Exception.Flatten().InnerExceptions)
{
sb.AppendLine(e.Message);
}
Console.WriteLine(sb.ToString());
}, TaskContinuationOptions.OnlyOnFaulted); parent.Start();
4,对于协作取消要用CancellationTokenSource类。
1)CancellationTokenSource.Token方法返回CancellationToken,可以将CancellationToken传入我们的工作方法,并查询CancellationToken.IsCancellationRequested属性来获得操作是否已取消。取消的情况下,可以结束工作方法。
2)一般在主线程调用取消方法。CancellationToeknSource.Cancel。
3)取消时可以加入回调方法,通过CancellationToken.Register方法注册。对于回调方法抛出的异常,可以捕捉Cancel方法,异常会被包装到AggregateException异常中,查询InnerExceptions可的异常的详细信息。
4)CancellationTokenSource的静态方法CreateLinkedTokenSource可以创建一个关联的CreateLinkedTokenSource对象。任意其中的一个CreateLinkedTokenSource被取消,这个关联的CreateLinkedTokenSource就会被取消。
5,任务调度器。
分为线程池任务调度器(thread pool task scheduler)和同步上下文任务调度器(synchroliazation context task scheduler)。其中同步上下文任务调度器能将所有的任务调度给UI线程,这对于更新界面的异步操作相当有用!默认的调度器是线程池任务调度器。
非UI线程更新UI界面会报错,可以用下面的方法,指定同步上下文任务调度器:
TaskScheduler syncSch = TaskScheduler.FromCurrentSynchronizationContext(); Task<int> t = new Task<int>(() => Sum(100)); //update UI with Synchronizationcontext
t.ContinueWith(task => Text = task.Result.ToString(), syncSch); t.Start();
6,非UI线程更新UI界面的方式总结
详见我的另一篇文章:
http://www.cnblogs.com/xiashengwang/archive/2012/08/18/2645541.html
7,Parallel
这个类提供了For,Foreach,Invoke静态方法。它内部封装了Task类。主要用于并行计算。
private void ParallelTest2()
{
for (int i = 1; i < 5; i++)
{
Console.WriteLine(DoWork(i));
}
//和上面的代码等价,但是是多线程并行执行的,注意这里的结束index不包含5
var plr = Parallel.For(1, 5, i => Console.WriteLine(DoWork(i)));
} private int DoWork(int num)
{
int sum = 0;
for (int i = 0; i <= num; i++)
{
sum += i;
}
return sum;
}
线程阶段性总结——APM,ThreadPool,Task,TaskScheduler ,CancellationTokenSource的更多相关文章
- 异步和多线程,委托异步调用,Thread,ThreadPool,Task,Parallel,CancellationTokenSource
1 进程-线程-多线程,同步和异步2 异步使用和回调3 异步参数4 异步等待5 异步返回值 5 多线程的特点:不卡主线程.速度快.无序性7 thread:线程等待,回调,前台线程/后台线程, 8 th ...
- 线程(Thread,ThreadPool)、Task、Parallel
线程(Thread.ThreadPool) 线程的定义我想大家都有所了解,这里我就不再复述了.我这里主要介绍.NET Framework中的线程(Thread.ThreadPool). .NET Fr ...
- 从Thread,ThreadPool,Task, 到async await 的基本使用方法解读
记得很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题 ...
- .net 多线程 Thread ThreadPool Task
先准备一个耗时方法 /// <summary>/// 耗时方法/// </summary>/// <param name="name">< ...
- 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 ...
- Thread,ThreadPool,Task, 到async await 的基本使用方法和理解
很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题吧. ...
- 异步多线程 Thread ThreadPool Task
一.线程 Thread ThreadPool 线程是Windows任务调度的最小单位,线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针.程序计数器等),但代码区是共享的,即不同的线程可以 ...
- C# 多线程学习系列三之CLR线程池系列之ThreadPool
一.CLR线程池 1.进程和CLR的关系一个进程可以只包含一个CLR,也可以包含多个CLR2.CLR和AppDomain的关系一个CLR可以包含多个AppDomain3.CLR和线程池的关系一个CLR ...
- C#线程学习笔记七:Task详细用法
一.Task类简介: Task类是在.NET Framework 4.0中提供的新功能,主要用于异步操作的控制.它比Thread和ThreadPool提供了更为强大的功能,并且更方便使用. Task和 ...
随机推荐
- OpenPAI大规模人工智能平台安装部署文档
环境要求: 如果需要图形界面,需要在Ubuntu系统安装,否则centos系统安装时是没有问题的(web端和命令行进行任务提交) 安装过程需要有另外一台控制端机器(注意:区别于集群所在的任何一台服务器 ...
- 今日头条 2018 AI Camp 5 月 26 日在线笔试编程题第一道——最佳路径
题目 给定一个 n*m 的矩阵 A ,矩阵中每一个元素为一个十六进制数.寻找一条从左上角都右下角的路径,每次只能向右或者向下移动, 使得路径上所有数字之积在 16 进制下的后缀 0 最少. 输入描述: ...
- 《机器学习实战》笔记——决策树(ID3)
现在要介绍的是ID3决策树算法,只适用于标称型数据,不适用于数值型数据. 决策树学习算法最大的优点是,他可以自学习,在学习过程中,不需要使用者了解过多的背景知识.领域知识,只需要对训练实例进行较好的标 ...
- [leetcode-663-Equal Tree Partition]
Given a binary tree with n nodes, your task is to check if it's possible to partition the tree to tw ...
- 水仙花数---基于python
# coding:utf-8"""水仙花数是指一个 n 位数(n≥3 ),它的每个位上的数字的 n 次幂之和等于它本身(例如:1^3 + 5^3+ 3^3 = 153) ...
- Python中的reload函数
Python中的import语句可以导入module文件,但是import语句只是第一次导入的时候会执行module文件中的代码,然后就会把导入的模块文件存入到内存,当再次导入的时候,Python是直 ...
- 软工第三次作业——个人PSP
9.22--9.26本周例行报告 1.PSP(personal software process )个人软件过程. 类型 任务 预计时间 开始时间 结束时间 中断时间 实际用时 准备工作 学习重定向 ...
- wf效能分析
听从了老师的建议我请教了其他的同学,修改了代码实现了功能四,以下是我的效能测试: 1.采用ptime.exe测试的3次截图 可以看到的是三次执行时间分别为:1.449秒:0.915秒:0.871秒,取 ...
- 20145214 《Java程序设计》第4周学习总结
20145214 <Java程序设计>第4周学习总结 教材学习内容总结 继承 继承基本上就是避免多个类间重复定义共同行为.要避免在程序设计上出现重复,可以把相同的程序代码提升为父类. 关键 ...
- unity像素贪吃蛇
[ 星 辰 · 别 礼 ] 设计过程: 首先,在之前玩坏控制台做的那个c#贪吃蛇之后,我以为做unity会很简单,但事实比较不如人意...拖了好几天.因为过程中遇到一些问题 蛇身的移动,还是用列表,将 ...