除了上篇中提到的线程池,本篇介绍一种新的实现异步操作的方法--任务(Task)。

主要内容:

  • 任务的介绍
  • 任务的基本应用
  • 子任务和任务工厂
  • 任务调度器
  • 并行任务Parallel

1. 任务的介绍

利用ThreadPool的QueueUserWorkItem方法建立的异步操作存在一些限制:

  1. 异步操作没有返回值
  2. 没有内建的机制来通知异步操作什么时候完成

而使用任务(Task)来建立异步操作可以克服上述限制,同时还解决了其他一些问题。

任务(Task)对象和线程池相比,多了很多状态字段和方法,便于更好的控制任务(Task)的运行。

当然,任务(Task)提供大量的功能也是有代价的,意味着更多的内存消耗。所以在实际使用中,如果不用任务(Task)的附加功能,那么就使用ThreadPool的QueueUserWorkItem方法。

通过任务的状态(TaskStatus),可以了解任务(Task)的生命周期。

TaskStatus是一个枚举类型,定义如下:

public enum TaskStatus
{   // 运行前状态Created = 0,                      // 任务被显式创建,通过Start()开始这个任务WaitingForActivation = 1,         // 任务被隐式创建,会自动开始WaitingToRun = 2,                 // 任务已经被调度,但是还没有运行// 运行中状态Running = 3,                      // 任务正在运行WaitingForChildrenToComplete = 4, // 等待子任务完成// 运行完成后状态RanToCompletion = 5,              // 任务正常完成Canceled = 6,                     // 任务被取消Faulted = 7,                      // 任务出错
}

构造一个Task后,它的状态为Create

启动后,状态变为WaitingToRun

实际在一个线程上运行时,状态变为Running

运行完成后,根据实际情况,状态变为RanToCompletiionCanceledFaulted三种中的一种。

如果Task不是通过new来创建的,而是通过以下某个函数创建的,那么它的状态就是WaitingForActivation

ContinueWithContinueWhenAllContinueWhenAnyFromAsync。

如果Task是通过构造一个TaskCompletionSource<TResult>对象来创建的,该Task在创建时也是处于WaitingForActivation状态。

2. 任务的基本应用

下面演示任务的创建,取消,等待等基本使用方法。

2.1 创建并启动一个Task

using System;
using System.Threading.Tasks;
using System.Threading;public class CLRviaCSharp_19
{static void Main(string[] args){Console.WriteLine("Main Thread start!");// 创建一个TaskTask t1 = new Task(() => { Console.WriteLine("Task start"); Thread.Sleep(1000);Console.WriteLine("Task end");});// 启动Taskt1.Start();// 主线程并没有等待Task,在Task完成前就已经完成了Console.WriteLine("Main Thread end!");Console.ReadKey(true);}
}

2.2 主线程等待子线程完成

using System;
using System.Threading.Tasks;
using System.Threading;public class CLRviaCSharp_19
{static void Main(string[] args){Console.WriteLine("Main Thread start!");// 创建2个TaskTask t1 = new Task(() => { Console.WriteLine("Task1 start"); Thread.Sleep(1000);Console.WriteLine("Task1 end");});Task t2 = new Task(() =>{Console.WriteLine("Task2 start");Thread.Sleep(2000);Console.WriteLine("Task2 end");});// 启动Taskt1.Start();t2.Start();// 当t1和t2中任何一个完成后,主线程继续后面的操作// Task.WaitAny(new Task[] { t1, t2 });// 当t1和t2中全部完成后,主线程继续后面的操作Task.WaitAll(new Task[] { t1, t2 });Console.WriteLine("Main Thread end!");Console.ReadKey(true);}
}

等待的方法WaitAllWaitAny可根据应用场景选用一个。

2.3 取消Task

取消Task和取消一个线程类似,使用CancellationTokenSource

using System;
using System.Threading.Tasks;
using System.Threading;public class CLRviaCSharp_19
{static void Main(string[] args){Console.WriteLine("Main Thread start!");CancellationTokenSource cts = new CancellationTokenSource();// 创建2个TaskTask t1 = new Task(() => { Console.WriteLine("Task1 start");for (int i = 0; i < 100; i++){if (!cts.Token.IsCancellationRequested){Console.WriteLine("Count : " + i.ToString());Thread.Sleep(1000);}else{Console.WriteLine("Task1 is Cancelled!");break;}}Console.WriteLine("Task1 end");}, cts.Token);// 启动Taskt1.Start();Thread.Sleep(3000);// 运行3秒后取消Taskcts.Cancel();// 为了测试取消操作,主线程等待Task完成Task.WaitAny(new Task[] { t1 });Console.WriteLine("Main Thread end!");Console.ReadKey(true);}
}

3. 子任务和任务工厂

3.1 延续任务

为了保证程序的伸缩性,应该尽量避免线程阻塞,这就意味着我们在等待一个任务完成时,最好不要用Wait,而是让一个任务结束后自动启动它的下一个任务。

using System;
using System.Threading.Tasks;
using System.Threading;public class CLRviaCSharp_19
{static void Main(string[] args){Console.WriteLine("Main Thread start!");// 第一个TaskTask<int> t1 = new Task<int>(() =>{Console.WriteLine("Task 1 start!");Thread.Sleep(2000);Console.WriteLine("Task 1 end!");return 1;});// 启动第一个Taskt1.Start();// 因为TaskContinuationOptions.OnlyOnRanToCompletion,// 所以第一个Task正常结束时,启动第二个Task。// TaskContinuationOptions.OnlyOnFaulted,则第一个Task出现异常时,启动第二个Task// 其他可详细参考TaskContinuationOptions定义的各个标志t1.ContinueWith(AnotherTask, TaskContinuationOptions.OnlyOnRanToCompletion);Console.WriteLine("Main Thread end!");Console.ReadKey(true);}// 第二个Task的处理都在AnotherTask函数中,// 第二个Task的引用其实就是上面ContinueWith函数的返回值。// 这里没有保存第二个Task的引用private static void AnotherTask(Task<int> task){Console.WriteLine("Task 2 start!");Thread.Sleep(1000);Console.WriteLine("Task 1's return Value is : " + task.Result);Console.WriteLine("Task 2 end!");}
}

3.2 子任务

定义子任务时,注意一定要加上TaskCreationOptions.AttachedToParent,这样父任务会等待子任务执行完后才结束。

using System;
using System.Threading.Tasks;
using System.Threading;public class CLRviaCSharp_19
{static void Main(string[] args){Console.WriteLine("Main Thread start!");Task<int[]> parentTask = new Task<int[]>(() =>{var result = new int[3];// 子任务1new Task(() => { Console.WriteLine("sub task 1 start!"); Thread.Sleep(1000);Console.WriteLine("sub task 1 end!");result[0] = 1;}, TaskCreationOptions.AttachedToParent).Start();// 子任务2new Task(() =>{Console.WriteLine("sub task 2 start!");Thread.Sleep(1000);Console.WriteLine("sub task 2 end!");result[1] = 2;}, TaskCreationOptions.AttachedToParent).Start();// 子任务3new Task(() =>{Console.WriteLine("sub task 3 start!");Thread.Sleep(1000);Console.WriteLine("sub task 3 end!");result[2] = 3;}, TaskCreationOptions.AttachedToParent).Start();return result;});parentTask.Start();Console.WriteLine("Parent Task's Result is :");foreach (int result in parentTask.Result)Console.Write("{0}\t", result);Console.WriteLine();Console.WriteLine("Main Thread end!");Console.ReadKey(true);}
}

上面的例子中,可以把TaskCreationOptions.AttachedToParent删掉试试,打印出来的Result应该是3个0,而不是1  2   3

3个子任务的执行顺序也和定义的顺序无关,比如任务3可能最先执行(与CPU的调度有关)。

3.3 任务工厂

除了上面的方法,还可以使用任务工厂来批量创建任务。

using System;
using System.Threading.Tasks;
using System.Threading;public class CLRviaCSharp_19
{static void Main(string[] args){Console.WriteLine("Main Thread start!");Task<int[]> parentTask = new Task<int[]>(() =>{var result = new int[3];TaskFactory tf = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.None);// 子任务1tf.StartNew(() =>{Console.WriteLine("sub task 1 start!");Thread.Sleep(1000);Console.WriteLine("sub task 1 end!");result[0] = 1;});// 子任务2tf.StartNew(() =>{Console.WriteLine("sub task 2 start!");Thread.Sleep(1000);Console.WriteLine("sub task 2 end!");result[1] = 2;});// 子任务3tf.StartNew(() =>{Console.WriteLine("sub task 3 start!");Thread.Sleep(1000);Console.WriteLine("sub task 3 end!");result[2] = 3;});return result;});parentTask.Start();Console.WriteLine("Parent Task's Result is :");foreach (int result in parentTask.Result)Console.Write("{0}\t", result);Console.WriteLine();Console.WriteLine("Main Thread end!");Console.ReadKey(true);}
}

使用任务工厂与上面3.2中直接定义子任务相比,优势主要在于可以共享子任务的设置,比如在TaskFactory中设置了TaskCreationOptions.AttachedToParent,那么它启动的子任务都具有这个属性了。

当然,任务工厂(TaskFactory)还提供了很多控制子任务的函数,用的时候可以看看它的类定义。

4. 任务调度器

上面例子中任务的各种操作(运行,等待,取消等等),都是由CLR的任务调度器来调度的。

FCL公开了2种任务调度器:线程池任务调度器同步上下文任务调度器

默认情况下,应用程序都是使用的线程池任务调度器。WPF和Winform中通常使用同步上下文任务调度器

CLR的任务调度器类(TaskScheduler)中有个Default属性返回的就是线程池任务调度器

还有个FromCurrentSynchronizationContext方法,返回的是同步上下文任务调度器

我们也可以通过继承CLR中的任务调度器(TaskScheduler)来定制适合自己业务需要的任务调度器。

下面我们定制一个简单的TaskScheduler,将3.3中每个子任务的打印信息的功能移到自定义的任务调度器MyTaskScheduler中。

using System;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Generic;public class CLRviaCSharp_19
{static void Main(string[] args){Console.WriteLine("Main Thread start!");Task<int[]> parentTask = new Task<int[]>(() =>{var result = new int[3];// 这里的TaskFactory中指定的是自定义的任务调度器MyTaskSchedulerTaskFactory tf = new TaskFactory(CancellationToken.None, TaskCreationOptions.AttachedToParent,TaskContinuationOptions.None, new MyTaskScheduler());// 子任务1tf.StartNew(() =>{Thread.Sleep(1000);result[0] = 1;});// 子任务2tf.StartNew(() =>{Thread.Sleep(1000);result[1] = 2;});// 子任务3tf.StartNew(() =>{Thread.Sleep(1000);result[2] = 3;});return result;});parentTask.Start();Console.WriteLine("Parent Task's Result is :");foreach (int result in parentTask.Result)Console.Write("{0}\t", result);Console.WriteLine();Console.WriteLine("Main Thread end!");Console.ReadKey(true);}
}// 自定义的TaskScheduler,没什么实际的作用,只是为了实验自定义TaskScheduler
public class MyTaskScheduler : TaskScheduler
{private IList<Task> _lstTasks;public MyTaskScheduler(){_lstTasks = new List<Task>();}#region inherit from TaskSchedulerprotected override System.Collections.Generic.IEnumerable<Task> GetScheduledTasks(){return _lstTasks;}protected override void QueueTask(Task task){_lstTasks.Add(task);// 将原先的打印信息,移到此处统一处理Console.WriteLine("task " + task.Id + " is start!");TryExecuteTask(task);Console.WriteLine("task " + task.Id + " is end!");}protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued){return TryExecuteTask(task);}#endregion
}

5. 并行任务Parallel

Parallel是为了简化任务编程而新增的静态类,利用Parallel可以将平时的循环操作都并行起来。

下例演示了for并行循环,foreach并行循环与之类似。

using System;
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;public class CLRviaCSharp_19
{static void Main(string[] args){Console.WriteLine("Main Thread start!");int max = 10;// 普通循环long start = Stopwatch.GetTimestamp();for (int i = 0; i < max; i++){Thread.Sleep(1000);}Console.WriteLine("{0:N0}", Stopwatch.GetTimestamp() - start);// 并行的循环start = Stopwatch.GetTimestamp();Parallel.For(0, max, i => { Thread.Sleep(1000); });Console.WriteLine("{0:N0}", Stopwatch.GetTimestamp() - start);Console.WriteLine("Main Thread end!");Console.ReadKey(true);}
}

在上面的例子中,采用并行循环消耗的时间不到原先的一半。

但是,采用并行循环需要满足一个条件,就是for循环中的内容能够并行才行

比如for循环中是个对 循环变量i 进行的累加操作(例如sum += i;),那就不能使用并行循环。

还有一点需要注意,Parallel的方法本身有开销

所以如果for循环内的处理比较简单的话,那么直接用for循环可能更快一些。

比如将上例中的Thread.Sleep(1000);删掉,再运行程序发现,直接for循环要快很多。

转载于:https://www.cnblogs.com/wang_yb/archive/2011/11/10/2244745.html

《CLR Via C# 第3版》笔记之(十九) - 任务(Task)相关推荐

  1. python数据挖掘学习笔记】十九.鸢尾花数据集可视化、线性回归、决策树花样分析

    #2018-04-05 16:57:26 April Thursday the 14 week, the 095 day SZ SSMR python数据挖掘学习笔记]十九.鸢尾花数据集可视化.线性回 ...

  2. 【CS231n】斯坦福大学李飞飞视觉识别课程笔记(十九):卷积神经网络笔记(下)

    [CS231n]斯坦福大学李飞飞视觉识别课程笔记 由官方授权的CS231n课程笔记翻译知乎专栏--智能单元,比较详细地翻译了课程笔记,我这里就是参考和总结. [CS231n]斯坦福大学李飞飞视觉识别课 ...

  3. 深度学习入门笔记(十九):卷积神经网络(二)

    欢迎关注WX公众号:[程序员管小亮] 专栏--深度学习入门笔记 声明 1)该文章整理自网上的大牛和机器学习专家无私奉献的资料,具体引用的资料请看参考文献. 2)本文仅供学术交流,非商用.所以每一部分具 ...

  4. Python学习笔记(十九)面向对象 - 继承

    Python学习笔记(十九)面向对象 - 继承 一.继承的概念 # 继承:子类继承父类的所有方法和属性# 1. 子类 class A(object):def __init__(self):self.n ...

  5. 《Linux内核设计与实现》读书笔记(十九)- 可移植性

    linux内核的移植性非常好, 目前的内核也支持非常多的体系结构(有20多个). 但是刚开始时, linux也只支持 intel i386 架构, 从 v1.2版开始支持 Digital Alpha, ...

  6. 【Visual C++】游戏开发笔记三十九 浅墨DirectX教程之七 他山之石:几种几何体的快捷绘制法

    本篇文章里,我们对Direct3D之中几种几何体的简洁绘制方法进行了详细的剖析,最后依旧是提供文章配套的详细注释的demo源代码的欣赏,并在文章末尾提供了源代码下载.(这标题有些歧义的,这个几种是修饰 ...

  7. JSP学习笔记(四十九):抛弃POI,使用iText生成Word文档

    POI操作excel的确很优秀,操作word的功能却不敢令人恭维.我们可以利用iText生成rtf文档,扩展名使用doc即可. 使用iText生成rtf,除了iText的包外,还需要额外的一个支持rt ...

  8. SLAM学习笔记(十九)开源3D激光SLAM总结大全——Cartographer3D,LOAM,Lego-LOAM,LIO-SAM,LVI-SAM,Livox-LOAM的原理解析及区别

    本文为我在浙江省北大信研院-智能计算中心-情感智能机器人实验室-科技委员会所做的一个分享汇报,现在我把它搬运到博客中. 由于参与分享汇报的同事有许多是做其他方向的机器人工程师(包括硬件.控制等各方面并 ...

  9. 图形学笔记(十九)动画 —— 动画的历史、关键帧插值、物理仿真、质点弹簧系统、粒子系统、(反向IK)动力学、Rigging 绑定、Blend Shapes、动作捕捉

    图形学笔记(十八)光场.颜色和感知-- 光场相机(全光函数.光线和光场的定义).可见光谱.谱功率密度.颜色的生物学基础.Tristimulus Theory.同色异谱.加色与减色系统.颜色空间SPD ...

  10. NET CLR via c# 第4版笔记 第19章 可空值类型

    System.Nullable<T> 是结构. 19.1 C# 对可空值类型的支持 C# 允许用问号表示法来声明可空值类型,如: Int32? x = 5;Int32? y = null; ...

最新文章

  1. 进阶指令——df指令(作用:查看磁盘的空间)、free指令(作用:查看内存使用情况)、head指令(作用:查看一个文件的前n行,如果不指定n,则默认显示前10行)、tail指令、less指令
  2. java架构程序员月入破3万到底是怎么炼成的,一篇文章让你了解
  3. rs232 python_利用python发出rs232信号
  4. 如果每一种语言都对应一种女生,你会喜欢哪一个?
  5. Java——递归练习
  6. 北大提出Gate Decorator,利用LSTM思想来做CNN剪枝
  7. 压缩UI深度的代码实现
  8. python编程从入门到实践pdf_【送书PDF】Python编程从入门到实践
  9. 2022精选最新金融银行面试真题——附带答案
  10. wps word文字样式管理
  11. oracle查询路径,查询oracle表空间路径
  12. ISA防火墙基础及应用
  13. 校园助手APP--简介及框架
  14. redis:Unable to connect to localhost:6379
  15. 用ADC0809实现八通道采集
  16. 微信小程序 报错一大串 define is not defined
  17. Linux查看so文件信息
  18. 微软准备再次裁员2850人 一年之内完成
  19. Xcode的 发展史
  20. html 英文字号,HTML,CSS,font-family:中文字体的英文名称 (宋体 微软雅黑)

热门文章

  1. onsrcoll和scrollTop兼容与实现
  2. Swift学习笔记(8)--函数
  3. SqlServer判断数据库、表、存储过程、函数是否存在
  4. BASH启动脚本及其启动顺序
  5. 改善ASP.NET2.0性能的五件法宝
  6. PMCAFF产品经理第一课 | 「在行」价值1.5万元的强大课程体系,365天能力突围
  7. 【人物】雷军去了联想,干货分享全场哑口无言,除了掌声...
  8. 利用jvisualvm分析JVM,进行性能调优
  9. web前端 学习线路
  10. 使用CSS预处理器Less