.Net Task常见问题
最近尝试使用一下Task,但是使用过程中因为API的不熟悉碰到了很多问题,不清楚什么时间来调用Task.Start(),具体该怎么使用等等。
如下所描述的Task.Start()方法均为实例方法。
1. 什么时候使用Task.Start()方法?
Task的Start方法当且仅当Task的状态是Created状态的时候才能使用。而想让Task的状态是Created的状态的话,只要通过任何Task的构造函数即可,比如说var t = new Task(someDelegate);
Task的状态包含如下:
public enum TaskStatus
{
Created,
WaitingForActivation,
WaitingToRun,
Running,
WaitingForChildrenToComplete,
RanToCompletion,
Canceled,
Faulted
}Task的状态分为代码中显示的8种:
Created就是前面提到的,当实例化的时候,Task的状态就是Created状态。
WaitingForActivation代表着该Task已经被Start了,但是等待进入.Net的调度结构
WaitingToRun表示Task已经被调度了,只是还没有开始执行
Running表示该Task正在运行,但是还没有执行结束
WaitingForChildrenToComplete表示该Task其实已经完成了执行,但是等待其中附带的子Task执行结束
RanToCompletion表示Task成功执行完毕
Canceled表示任务执行时通过其自定义的CancellationToken发信号的时候抛出了OperationCancelledException,或者是Task的CancellationToken在任务执行之前就已经发送了信号
Faulted表示Task在执行过程中抛出了一个没有处理异常信息
从Task的状态我们可以知道,.Net的调度有些类似于线程调度器的作用,但是是基于线程的更具体的抽象,可以进行取消等操作。
2. 由Task.Run/Task.ContinueWith/Task.Factory.StartNew/TaskCompletionSource/异步方法等方法产生的Task,开发者是否需要调用Task.Start()方法?
答案是不需要。不仅仅是不应该,而是真的不能。。如前面的问题所提到的,Task仅仅在Created的状态才允许调用Task.Start()方法的,而通过提到的这些方法所创建的Task都已经不是Created的状态了,比如TaskStatus.WaitingForActivation, 或者 TaskStatus.Running, 或者TaskStatus.RanToCompletion等。
3. Task.Start()真正做了些什么?
当执行了Task.Start()之后,会将Task加入到TaskScheduler的队列之中(无参启动的话,队列为TaskScheduler.Current)。当开发者通过Task的构造函数构建了一个Task的时候,Task仍然是未激活状态,该Task仍然还没有开始调度,所以不会执行任何任务。如果不通过Task.Start()来启动任务,Task就永远不会进入队列,也永远不会完成。为了让Task能够顺利执行,开发者就必然要将其加入到调度队列之中,这样才能够让调度器在正常的时候进行执行任务。当执调用了Task.Start()的时候,Task会改变其状态(从Created到WaitingToRun状态),然后将Task送到指定的TaskScheduler上进行调度。到了这个时候,Task后续的执行就被交给TaskScheduler来完成了,只会会通过TaskScheduler的TryExecuteTask方法来执行。
4. 在同一个Task能否多次调用Task.Start()?
答案是否。一个Task只能从Created状态转变一次,也就是说Task.Start()只能执行一次。当Task不再处于Created状态之后,任何的Task.Start()调用都会抛出异常。Task.Start()方法通过同步来确保Task对象能够保持一致的状态,是否并发执行都不会有线程安全问题。
5. Task.Start()和Task.Factory.StartNew()之间有什么区别?
Task.Factory.StartNew()相当于new一个Task并调用Task.Start()的简写版本,代码如下:
// Task.Factory.StartNew()
var t = Task.Factory.StartNew(someDelegate);
// equivalent to
var t = new Task(someDelegate);
t.Start();
从性能的角度来说的话,前面的方式会更为高效一些。在第三个问题中描述过,当调用Task.Start()的时候有同步操作,来确保Task实例还没有启动,或者并发启动。相对的,在Task.Factory.StartNew()放阿飞中,.Net可以确定没有人并发的执行启动Task,所以不需要进行同步操作。
6. Task.Result是否可能也会启动Task?
答案是否。有且仅有两种方式来令一个处于Created状态的Task的状态进行改变:
- 传递
CancellationToken到Task的构造函数之中,并且CancellationToken已经或者结束了请求。如果当Task仍然在Created状态的时候前面的行为发生了,那么Task的状态会变为Canceled的状态。 - 在该Task上面调用
Task.Start()方法。
想令一个Task的状态从Created转变,就只有这两种方式。如果开发者在处于Created状态的Task上面使用Task.Wait()或者Task.Result方法的话,调用将会阻塞;只有其他地方调用了Task.Start()的时候才能将其加入TaskScheduler的队列之中进行调度,这样Task才可能完成,前面阻塞的调用才能被唤醒。
Task.Result方法不能启动Task,但是可能潜在的改变TaskScheduler针对Task的执行顺序。如果Task已经被TaskScheduler调度了,那么Task本身可能还在等待执行。当开发调用Task.Result方法时,运行时可能会尝试内联任务的执行(意味着在线程上执行任务),而不是会让TaskScheduler阻塞其他线程的执行来立即执行调用Task.Result的任务。执行了Task.Result只是会调用TaskScheduler的TryExecuteTaskInline()方法,之后便完全取决于TaskScheduler是如何调度了。
7. 开发者是否该从public API返回一个没有执行Start的Task?
实际的问题应该是,“我是否该返回一个状态为Created的Task?” 而答案很明显,就是不行。
基本的思想如下:当开发者调用一个普通的同步的方法时,调用会在调用的一刻就立即执行。但是对于那些返回Task的方法,我们可以认为哪个Task意味着方法的异步完成。但是这并不影响调用其相关的Start之类的操作。因此,如果返回一个处于Created状态的Task是很奇怪的,也表示Task并没有启动。
所以,如果开发者需要在一个公开的方法返回一个Task,那么,请在返回之前,调用Task的构造函数之后就直接Start这个Task。否则,你返回的Task没有执行的话,很容易产生一个死锁或者类似的问题。因为调用方可能在等待Task执行的完成来继续其他的操作的,而Task还没有开始执行,那么就永远不会结束的。一些框架的允许开发者来通过方法和回调来参数化框架,其返回的Task还会验证Task的状态的,如果返回的Task是Created的状态,就会抛出异常。
8. 开发者是否该使用Task的构造函数和Task.Start()方法?
在绝大多数的情况下,开发者最好不要通过Task的构造函数和Task.Start这种机制。比如,如果开发者只是想调度一个任务来执行一些代理,那么最好使用Task.Run或者Task.Factory.StartNew。不仅仅是因为Task.Run和Task.Factory.StartNew的代码更少,同样也是因为其性能更高(没有同步的代价),开发者也不容易出错,所以最好别使用Task的构造函数以及Task.Start()操作。
当然了,有很多场景使用Task的构造函数以及Task.Start()也是非常合理的。举例来说,如果开发者选择继承Task来做一些操作的话,那么开发者就需要使用Task.Start()方法来将其加入TaskScheduler的队列了。另一个例子就是,如果开发者想要使用Task本身的一些属性。比如如下的问题代码:
Task theTask = null;
theTask = Task.Run(() => Console.WriteLine(“My ID is {0}.”, theTask.Id));
上面的代码会有瑕疵,那就是竞争。在调用Task.Run()的时候,会创建一个Task对象并且将其加入线程池的调度队列中,但是如果当前有足够的线程资源,会立刻从线程池中挑选线程来立刻创建Task并执行。那个线程会立刻进入主线程的变量theTask来调用Task.Run,但是创建的Task可能还没有写入到theTask变量,但是分开就可以解决这个问题,代码如下:
Task theTask = null;
theTask = new Task(() =>Console.WriteLine(“My ID is {0}.”, theTask.Id));
theTask.Start(TaskScheduler.Default);
现在,我们就可以确定上面的代码不会抛出空指针异常,因为Task在执行前就已经写入了theTask变量之中,前面已经提到了,不执行Task.Start()的话,Task是不会进入调度的,所以就不会出现竞争的问题。
.Net Task常见问题的更多相关文章
- 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-如何添加自定义Task,如何让程序的一部分拥有不同的执行周期
右击Tasks,添加一个新的Task,可以设置这个新的任务的扫描周期,比如100ms 右击PLC的整个的Project,然后Add一个Referenced Task,选中你新建的Task 在P ...
- Kerberos简介及常见问题
基本描述 Kerberos使用Needha-Schroeder协议作为它的基础.它使用了一个由两个独立的逻辑部分:认证服务器和票据授权服务器组成的"可信赖的第三方",术语称为密钥分 ...
- Maven使用常见问题整理
Maven使用常见问题整理 1.更新eclipse的classpath加入新依赖 1.在dependencyManagement里面加入包括版本在内的依赖信息,如: <dependenc ...
- 关于Windows Azure的常见问题-一般问题FAQ
一般问题 什么是Windows Azure? Windows Azure 是一个灵活而开放的云平台,通过该平台,您可以在数据中心快速生成.部署和管理应用程序.Windows Azure 支持所有主流操 ...
- .NET 并行(多核)编程系列之六 Task基础部分完结篇
原文:.NET 并行(多核)编程系列之六 Task基础部分完结篇 .NET 并行(多核)编程系列之六 Task基础部分完结篇 前言:之前的文章介绍了了并行编程的一些基本的,也注重的讲述了Task的一些 ...
- .NET 4 并行(多核)编程系列之四 Task的休眠
原文:.NET 4 并行(多核)编程系列之四 Task的休眠 .NET 4 并行(多核)编程系列之四 Task的休眠 前言:之前的几篇文章断断续续的介绍了Task的一些功能:创建,取消.本篇介绍Tas ...
- Confluence 使用常见问题列表
Confluence 6 管理 Atlassian 提供的 App 摘要: Confluence 用户可以使用桌面应用来编辑一个已经上传到 Confluence 的文件,然后这个文件自动保存回 Con ...
- grunt入门讲解5:创建插件,安装Grunt以及常见问题
创建插件 创建插件主要有以下几个步骤: (1)通过 npm install -g grunt-init 命令安装 grunt-init .(2)通过 git clone git://github.co ...
- Hive常见问题汇总
参考资料: Hive常见问题汇总 啟動hive出錯,提示沒有權限 2015年04月02日 09:58:49 阅读数:31769 这里小编汇集,使用Hive时遇到的常见问题. 1,执行#hive命令进入 ...
随机推荐
- Java基本语法和变量
1基本语法 1.1 标识符.关键字 在程序中用于定义名称的都为标识符,如文件名称.类名称.方法名称或变量名称等. 在Java中标识符的定义格式由字母.数字._(下划线),$所组成,不能以数字开头, 不 ...
- Servlet之sendRedirect和getRequestDispatch
Servlet的请求重定向和请求转发方法的比较分析: 1.getRequestDispatch是属于httpServletRequest对象的方法,请求转发是在同一个请求中完成的,因此整个过程只包含一 ...
- cf1027F. Session in BSU(并查集 匈牙利)
题意 题目链接 $n$个人,每个人可以在第$a_i$天或第$b_i$,一天最多考一场试,问在最优的情况下,最晚什么时候结束 Sol 自己只能想到暴力匈牙利二分图匹配,然而还是被构造数据卡了.. 标算很 ...
- jquery中使用each遍历。
一直知道each这个方法,但是就是不太明白到底怎么用,今天两个地方都使用了each.真的太高兴了,太有成就感了. 东钿微信平台订单列表页 全部订单之前是按照产调,评估,借款的顺序依次排下来,华总说要按 ...
- python-day1作业(感谢视频老师留的作业)
__author__ = 'zht' #!/usr/bin/env python # -*- coding: utf-8 -*- ''' #努力学习每一天 ''' #尝试次数计数器 tries = 0 ...
- MySQL表的碎片整理和空间回收小结
MySQL表碎片化(Table Fragmentation)的原因 关于MySQL中表碎片化(Table Fragmentation)产生的原因,简单总结一下,MySQL Engine不同,碎片化的原 ...
- C基础的练习集及测试答案(1-15)
练习题:注:标有(课堂)字样的为课上练习,其他为课下练习基础题(50题)1.(课堂)编写程序,输出“XXX欢迎来到动物园!”(XXX是自己的名字). //1.(课堂)编写程序,输出“XXX欢迎来到动物 ...
- 总结一下自己脑海里的JavaScript吧(一)--DOM模型
今天是2019年6月25日,闲来无事,写一篇文章来看看自己脑袋里装了多少JavaScript知识! 这儿就第一章: 说起JavaScript,它是什么?后端脚本语言?前端编程语言?还是在网站浏览器上运 ...
- Nuget使用备忘
菜单:工具-库程序包管理器-管理解决方案的NuGet程序包,搜索,下载,安装 或者 工具-库程序包管理器-程序包管理器控制台,输入PM命令,如: install-package log4net 如果不 ...
- bootstrap 翻页的状态
翻页的状态 下面的实例演示了上表中所讨论的 class .disabled 的用法: <!DOCTYPE html><html><head><meta htt ...