CLR via C# 笔记 ----Task(任务) 1
直接给线程池添加工作项的方式很直接,但是无法得知工作项何时结束,并且不能获得返回值
于是,CLR提供了一个Task类封装了工作项
以下三种写法某种意义上来说是等价的
//1作为object传递(仅仅是举例的作用),当回调需求一个ojbect参数时,需要额外传递这个参数
ThreadPool.QueueUserWorkItem(DoSth,1);
new Task(DoSth, 1).Start();
Task.Run(()=>DoSth(1));
public void DoSth(object o) { /*DoSth*/}
这三种写法都无法获取返回值,只是为了展示Task和线程池工作项之间的关系
实际的Task用法可能是这样
public int DoSth()
{
//做一些耗时工作
Thread.Sleep(2000);
//返回一个结果
return 1;
}
Task<int> task1 = new Task<int>(DoSth);
//task创建好后,可以在任意时刻start
task1.Start();
//调用task的Result属性 获取返回值 通常还需要try catch处理 先略过
Console.WriteLine(task1.Result);
很好,可以获取工作项的返回值了,但是这种获取方式有问题,直接调用task的Result属性 会造成调用线程阻塞 (需要等待工作项完成)
这和多线程使用的初衷根本不相符。
所以Task有一个方法用来注册任务完成时的回调(延续任务),参数是task本身
task1.ContinueWith(t => Console.WriteLine(t.Result));
调用这个方法不会造成任何阻塞,而传进去的回调 在工作项结束后会被另一个线程执行
同时,还可额外传递一个TaskContinuationOptions类型的枚举,来指定此延续任务的执行方式
[Flags]
public enum TaskContinuationOptions
{
None = 0,//默认
PreferFairness = 1,//提意快速执行 (因为延续任务本质上还是任务,会由线程池的调度器调度)
LongRunning = 2,//书中描述 提意由尽可能由线程池线程执行 (但上文又说延续任务都由线程池负责...)
AttachedToParent = 4,//将延续任务作为上一个任务的子任务
DenyChildAttach = 8,//拒绝Attach (以此延续任务为父任务)
HideScheduler = 16,//启用默认调度器 (任务可以指定一个调度器,默认情况下,延续任务使用上一个任务的调度器)
LazyCancellation = 32,//上一个任务被取消了的话,本延续任务取消
RunContinuationsAsynchronously = 64,//用其他线程执行延续任务
NotOnRanToCompletion = 65536,
NotOnFaulted = 131072,
OnlyOnCanceled = 196608,//只有在上一个任务取消的时候 才执行
NotOnCanceled = 262144,
OnlyOnFaulted = 327680,//上一个任务异常时才执行
OnlyOnRanToCompletion = 393216,//上一个任务顺利完成时才执行
ExecuteSynchronously = 524288//同步执行延续任务(但不阻塞 原理不明..)
}
如果不指定标志,延续任务总会执行
最后提一嘴子任务,子任务就是在任务的函数中 又创建的任务,并且在TaskCreationOptions.AttachedToParent(注意这个枚举和上面那个不同...枚举中的标志部分相同,毕竟本质上都是在创建一个任务)(默认任何地方创建的任务都是顶级任务,除非指定标志)
父任务需要等待子任务全部完成才能算完成。