最近尝试使用一下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. 远程调试工具weinre使用教程

    一:前言 我们都知道,chrome的开发者工具(f12)是一个方便我们调试PC页面的工具.但是现在我们的开发离不开移动端,那如果我们需要对手机页面进行调试,那该怎么办了? 当然,chrome的开发者工 ...

  2. 《javascript设计模式》笔记之第八章:桥接模式

    个人理解:桥接模式就是更进一步地封装已有api,通过这个封装连接你的输入和底层api(初步理解,以后加深理解有不同体会之后可能要修改) 一:示例:事件监听器 下面这个示例就演示了通过一座桥(也就是一个 ...

  3. 电脑Bois中usb模式启动热键

    组装机主板 品牌笔记本 品牌台式机 主板品牌 启动按键 笔记本品牌 启动按键 台式机品牌 启动按键 华硕主板 F8 联想笔记本 F12 联想台式机 F12 技嘉主板 F12 宏基笔记本 F12 惠普台 ...

  4. Kendo MVVM 数据绑定(十一) Value

    Kendo MVVM 数据绑定(十一) Value Value 绑定可以把 ViewModel 的某个属性绑定到 DOM 元素或某个 UI 组件的 Value 属性.当用户修改 DOM 元素或 UI ...

  5. python3操作mysql数据库表01(封装查询单条、多条数据)

    #!/usr/bin/env python# -*- coding:UTF-8 -*- import pymysql# import os'''封装查询单条.多条数据'''# os.environ[' ...

  6. HTTP错误码汇总(转)

    响应码由三位十进制数字组成,它们出现在由HTTP服务器发送的响应的第一行.响应码分五种类型,由它们的第一位数字表示:1.1xx:信息,请求收到,继续处理2.2xx:成功,行为被成功地接受.理解和采纳3 ...

  7. Mongodb之failed to create service entry worker thread

    Mongodb "failed to create service entry worker thread" 错误. 系统:CentOS release 6.8 mongod.lo ...

  8. halt, reboot, poweroff - 中止系统运行

    SYNOPSIS /sbin/halt [-n] [-w] [-d] [-f] [-i] [-p] /sbin/reboot [-n] [-w] [-d] [-f] [-i] /sbin/powero ...

  9. xshell5 上传下载命令

    借助XShell,使用linux命令sz可以很方便的将服务器上的文件下载到本地,使用rz命令则是把本地文件上传到服务器. yum -y install lrzsz 其中,对于sz和rz的理解与记忆我用 ...

  10. 如何使用动画库animate.css

    animate.css是一个CSS3动画库,里面预设了抖动(shake).闪烁(flash).弹跳(bounce).翻转(flip).旋转(rotateIn/rotateOut).淡入淡出(fadeI ...