前面 https://www.cnblogs.com/GreenLeaves/p/9986789.html 介绍了 Task 的由来, 以及简单的使用, 包括开启任务, 处理任务的超时, 异常, 取消, 以及如果获取任务的返回值, 在回去返回值之后, 立即唤起新的线程处理返回值, 且如果前面的任务发生异常, 唤起任务如果有效的处理异常等关于 Task 的知识. 所以本文将介绍 Task 更多的用法和特性.
一, 如果通过一个任务创建多个子任务.
1,Task 支持一个任务, 创建多个子任务, 并且保持关联.
- static void Main(string[] args)
- {
- var parentTask = new Task<int[]>(() =>
- {
- // 开启多个子任务
- var results = new int[2];
- // 创建子任务, 并将子任务的值赋给 results 变量, 并通过 TaskCreationOptions.AttachedToParent, 将其关联到父任务, 如果不指定, 该任务将独立于父任务单独执行
- // 这里有个奇怪的问题, 只能使用 new Task 的方式去创建关联到父任务的子任务, 因为 Task.Run 没有提供这个方法, 可以通过扩展方法解决这个问题
- new Task(() => results[0] = ChildThreadOne(), TaskCreationOptions.AttachedToParent).Start();
- new Task(() => results[1] = ChildThreadTwo(), TaskCreationOptions.AttachedToParent).Start();
- 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()
- {
- Thread.Sleep(2000);// 模拟长时间计算操作
- Console.WriteLine("子任务一完成了计算任务, 并返回值:{0}", 6);
- return 6;
- }
- /// <summary>
- /// 子任务一
- /// </summary>
- static int ChildThreadTwo()
- {
- Thread.Sleep(2000);// 模拟长时间计算操作
- Console.WriteLine("子任务二完成了计算任务, 并返回值:{0}", 6);
- return 6;
- }
二, 关于 Task 的资源释放问题.
如果你看过 Task 的源码, 你会发现下面这个有趣的问题:
ok, 你会想它想释放什么呢?
没错, 当 Task 任务, 指定了 TaskContinuationOptions 枚举状态, 且指定的值如下:
那么, 直接 return, 什么资源释放操作都不做.
如果任务没有完成, 就调用 Dispose 方法, 那么直接抛异常, 如果完成了, 它就释放了 ManualResetEventSlim 信号量(后面的文章会介绍). 所以如果你在 task 中使用了其它的一些非托管资源, 那么最好在代码里自己手动释放, 在使用完之后. 或者自己实现了 Task 的派生类, 把需要用的非托管资源加进去, 然后在使用完派生类之后, 调用 Dispose 方法.
三, 关于 Task 的几个常用属性
1,Id 属性, 每个 Task 对象都有一个 Id 属性, 全局唯一, 且每次创建新的任务, 这个值都会递增 1.
2,TaskStatus 状态
- //
- // 摘要:
- // 表示 System.Threading.Tasks.Task 的生命周期中的当前阶段.
- public enum TaskStatus
- {
- //
- // 摘要:
- // 该任务已初始化, 但尚未被计划.
- Created = 0,
- //
- // 摘要:
- // 该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划.
- WaitingForActivation = 1,
- //
- // 摘要:
- // 该任务已被计划执行, 但尚未开始执行.
- WaitingToRun = 2,
- //
- // 摘要:
- // 该任务正在运行, 但尚未完成.
- Running = 3,
- //
- // 摘要:
- // 该任务已完成执行, 正在隐式等待附加的子任务完成.
- WaitingForChildrenToComplete = 4,
- //
- // 摘要:
- // 已成功完成执行的任务.
- RanToCompletion = 5,
- //
- // 摘要:
- // 该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 对取消进行了确认, 此时该标记处于已发送信号状态; 或者在该任务开始执行之前, 已向该任务的
- // CancellationToken 发出了信号. 有关详细信息, 请参阅任务取消.
- Canceled = 6,
- //
- // 摘要:
- // 由于未处理异常的原因而完成的任务.
- Faulted = 7
- }
构造完 Task 对象是, 状态为 Created, 当任务启动时, 状态变为 WaitingToRun, 当 Task 实际在线程上运行时, 状态变为 Running. 如果当前任务为父任务, 且它已经执行完毕, 等待其它子任务执行完毕的时候, 其状态变为 WaitingForChildrenToComplete. 如果任务完成可能会出现以下几种状态: RanToCompletion(已成功完成执行的任务),Canceled(取消状态),
Faulted(任务出错).
这里需要注意一个特殊的状态 WaitingForActivation
当使用 Task 对象的 ContinueWith 的 Task 对象处理改状态, 意味者该 Task 任务的调度由任务基础结构控制. 也就是该任务的调度只有当前面的任务执行完之后, 由 CLR 发起执行调用.
C# 多线程六之 Task(任务)二
来源: http://www.bubuko.com/infodetail-2869240.html