最近尝试使用一下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会改变其状态(从CreatedWaitingToRun状态),然后将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的状态进行改变:

  1. 传递CancellationToken到Task的构造函数之中,并且CancellationToken已经或者结束了请求。如果当Task仍然在Created状态的时候前面的行为发生了,那么Task的状态会变为Canceled的状态。
  2. 在该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只是会调用TaskSchedulerTryExecuteTaskInline()方法,之后便完全取决于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.RunTask.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常见问题的更多相关文章

  1. 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-如何添加自定义Task,如何让程序的一部分拥有不同的执行周期

    右击Tasks,添加一个新的Task,可以设置这个新的任务的扫描周期,比如100ms   右击PLC的整个的Project,然后Add一个Referenced Task,选中你新建的Task   在P ...

  2. Kerberos简介及常见问题

    基本描述 Kerberos使用Needha-Schroeder协议作为它的基础.它使用了一个由两个独立的逻辑部分:认证服务器和票据授权服务器组成的"可信赖的第三方",术语称为密钥分 ...

  3. Maven使用常见问题整理

    Maven使用常见问题整理  1.更新eclipse的classpath加入新依赖  1.在dependencyManagement里面加入包括版本在内的依赖信息,如:   <dependenc ...

  4. 关于Windows Azure的常见问题-一般问题FAQ

    一般问题 什么是Windows Azure? Windows Azure 是一个灵活而开放的云平台,通过该平台,您可以在数据中心快速生成.部署和管理应用程序.Windows Azure 支持所有主流操 ...

  5. .NET 并行(多核)编程系列之六 Task基础部分完结篇

    原文:.NET 并行(多核)编程系列之六 Task基础部分完结篇 .NET 并行(多核)编程系列之六 Task基础部分完结篇 前言:之前的文章介绍了了并行编程的一些基本的,也注重的讲述了Task的一些 ...

  6. .NET 4 并行(多核)编程系列之四 Task的休眠

    原文:.NET 4 并行(多核)编程系列之四 Task的休眠 .NET 4 并行(多核)编程系列之四 Task的休眠 前言:之前的几篇文章断断续续的介绍了Task的一些功能:创建,取消.本篇介绍Tas ...

  7. Confluence 使用常见问题列表

    Confluence 6 管理 Atlassian 提供的 App 摘要: Confluence 用户可以使用桌面应用来编辑一个已经上传到 Confluence 的文件,然后这个文件自动保存回 Con ...

  8. grunt入门讲解5:创建插件,安装Grunt以及常见问题

    创建插件 创建插件主要有以下几个步骤: (1)通过 npm install -g grunt-init 命令安装 grunt-init .(2)通过 git clone git://github.co ...

  9. Hive常见问题汇总

    参考资料: Hive常见问题汇总 啟動hive出錯,提示沒有權限 2015年04月02日 09:58:49 阅读数:31769 这里小编汇集,使用Hive时遇到的常见问题. 1,执行#hive命令进入 ...

随机推荐

  1. JSON.stringify 语法讲解

    作用:这个函数的作用主要是为了系列化对象的. 可能有些人对系列化这个词过敏,我的理解很简单.就是说把原来是对象的类型转换成字符串类型(或者更确切的说是json类型的).就这么简单.打个比方说,你有一个 ...

  2. C#高级语法

    委托 委托就是指针函数,委托的定义与类的属性定义类似都必须在类的方法体进行. 委托的定义: class Program { //定义委托:委托不能在方法体内定义. public delegate st ...

  3. 【翻译转载】【官方教程】Asp.Net MVC4入门指南(5):从控制器访问数据模型

    在本节中,您将创建一个新的MoviesController类,并在这个Controller类里编写代码来取得电影数据,并使用视图模板将数据展示在浏览器里. 在开始下一步前,先Build一下应用程序(生 ...

  4. Redis key 键

    1.set key value    //设置.修改值 2.get key  //如果key不存在,返回nil,表示空. 3.type key   //返回key对应的value的数据类型 4.ren ...

  5. IO流----转换流、缓冲流

    打开一个文本文件,另存为: Ansi就是系统默认编码(就是gbk) 建一个编码是utf-8的txt文件, 例: import java.io.FileWriter; import java.io.IO ...

  6. 《移动Web前端高效开发实战》笔记2——使用Gulp构建一个ECMAScript 6和Sass应用

    8.3.1 安装和配置 运行Gulp需要Node.js环境,请参看第二章内容搭建Node.js环境.使用NPM全局安装Gulp,命令如下: npm install gulp-cli –g 然后,在项目 ...

  7. Vue Scroller:Vue 下拉刷新及无限加载组件

    Vue Scroller Vue Scroller is a foundational component ofVonic UI. In purpose of smooth scrolling, pu ...

  8. STM8 PIN setting(output)

    今日在设置引脚输出的时候,本想设置为open-drain输出,然后对其输出高低.但是发现无法输出高(初始化为开漏低电平),始终为低.后来改为push-pull 输出,就能输出高低了.真有意思,转到SP ...

  9. js读取excel数据后的时间格式转换

    使用xlsx.full.min.js 获取excel的日期数据为:37858: 拿到的整数值是日期距离1900年1月1日的天数,这时需要写一个函数转换: function formatDate(num ...

  10. -oN ,-oX,-oG

    -oN ,正常输出 -oX, xml输出 nmap  192.168.9.12 -oX TEST.xml -oG grep输出 html文件可读性比xml文件要好,将xml转换成html     xs ...