.Net Task常见问题-程序员宅基地

技术标签: c#  

最近尝试使用一下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是不会进入调度的,所以就不会出现竞争的问题。

转载于:https://www.cnblogs.com/qitian1/p/6461540.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30814319/article/details/99526054

智能推荐

leetcode sql题目_leedcode sql-程序员宅基地

文章浏览阅读392次。1 # Write your MySQL query statement belowselect max(salary) as SecondHighestSalary from Employee where salary not in (select max(salary) from Employee )Write a SQL query to get the second hig_leedcode sql

嵌入式软件工程师笔试面试指南-ARM体系与架构_嵌入式工程师笔试面试指南-程序员宅基地

文章浏览阅读1.1w次,点赞74次,收藏324次。嵌入式软件笔试,嵌入式软件面试,程序员简历书写,Linux驱动工程师笔试,Linux驱动工程师面试,BSP工程师笔试,BSP工程师面试,应届生秋招,应届生春招,C/C++笔试题目,C/C++面试题目,C/C++程序员,BSP工程师_嵌入式工程师笔试面试指南

威佐夫博弈 hdu1527 取石子游戏_博弈 分割石子-程序员宅基地

文章浏览阅读800次。传送门:点击打开链接题意:轮流取石子。1.在一堆中取任意个数.2.在两堆中取相同个数。最后取完的人胜利,问先手是否必赢思路:威佐夫博弈博弈,满足黄金分割,且每个数字只会出现一次。具体求法见代码#include#include#include#include#include#include#include#include#include#include#include_博弈 分割石子

python控制蓝牙音响_[ESP32+MicroPython]智能音响控制-程序员宅基地

文章浏览阅读2k次。blinker支持多种智能音响控制,如天猫精灵、百度小度、小米小爱、京东叮咚等。这里以天猫精灵控制为例,blinker DIY支持将设备模拟成三种类型的智能家居:插座、灯、传感器。Blinker支持多种语音助手控制,如天猫精灵、百度小度,本节以天猫精灵控制为例。示例程序及blinker模块天猫精灵基本接入方法通常语音助手都是对特定的设备类型进行支持,确定设备类型后,才能响应对应的语音指令。使用bl..._blinker支持micpython么

(赠源码)python+django+Mysql上课点名系统03391-计算机毕业设计-程序员宅基地

文章浏览阅读112次。对于本上课点名系统的设计来说,它主要是采用后台采用了B/S的结构,它是应用mysql数据库,python等技术动态编程以及数据库进行努力学习和大量实践,并运用到了整个系统的设计当中,具体根据网上上课点名系统的现状来进行开发的,具体根据学生需求实现网上上课点名系统网络化的管理,各类信息有序地进行存储,进入上课点名系统页面之后,方可开始操作主控界面,系统功能包括管理员服务端:后台首页、系统用户(管理员、任课老师、学校领导、班主任、学生注册)、模块管理(课程类别、课程信息、课程签到、公告信息、签到提醒)。

随便推点

Redis实现延迟队列方法介绍-程序员宅基地

文章浏览阅读3.3k次。其中,延迟队列是 Redis 的一个重要应用场景,它被广泛应用于异步任务的调度、消息队列的实现以及秒杀、抢购等高并发场景的处理。在实现延迟队列时,我们可以使用 Redis 的有序集合来保存待执行的任务,其中元素的分值表示任务的执行时间,元素的值表示任务的内容。使用 ZADD 命令将任务添加到有序集合中,将任务的执行时间作为元素的分值,将任务的内容作为元素的值。使用 ZADD 命令将任务添加到有序集合中,将任务的执行时间作为元素的分值,将任务的内容作为元素的值。一、Redis 有序集合实现延迟队列。

Python入门实战:Python的文件操作-程序员宅基地

文章浏览阅读701次,点赞23次,收藏7次。1.背景介绍Python是一种强大的编程语言,它具有简洁的语法和易于学习。Python的文件操作是一种常用的编程技术,可以让程序员更方便地读取和写入文件。在本文中,我们将深入探讨Python的文件操作,涵盖了核心概念、算法原理、具体操作步骤、数学模型公式、代码实例以及未来发展趋势。1.1 Python的文件操作背景Python的文件操作是一种基本的编程技能,它允许程序员在程序中读取和写...

机器学习模型对比_机器学习的模型比较-程序员宅基地

文章浏览阅读1k次。1.SVM和LR(逻辑回归)1.1 相同点都是线性分类器。本质上都是求一个最佳分类超平面。都是监督学习算法。 都是判别模型。通过决策函数,判别输入特征之间的差别来进行分类。常见的判别模型有:KNN、SVM、LR。 常见的生成模型有:朴素贝叶斯,隐马尔可夫模型。1.2 不同点损失函数不同,LR的损失函数为交叉熵;svm的损失函数自带正则化,而LR需要在损失函数的基础上加上正则化。 两个模型对数据和参数的敏感程度不同。SVM算法中仅支持向量起作用,大部分样本的增减对模型无影响;而L_机器学习的模型比较

纯C语言完整代码操作单链表(初始化、插入、删除、查找...)-程序员宅基地

文章浏览阅读901次,点赞3次,收藏10次。C语言操作单链表

实战打靶集锦-027-SoSimple1_sosimple 写入试验场-程序员宅基地

文章浏览阅读1.6k次,点赞32次,收藏47次。本文简单记录了博主的一次打靶经历,涉及wordpress扫描与爆破、social-warfare远程代码执行漏洞、sudo命令提权等_sosimple 写入试验场

用opencv的dnn模块做yolov5目标检测_opencv yolov5-程序员宅基地

文章浏览阅读7w次,点赞271次,收藏1.1k次。最近在微信公众号里看到多篇讲解yolov5在openvino部署做目标检测文章,但是没看到过用opencv的dnn模块做yolov5目标检测的。于是,我就想着编写一套用opencv的dnn模块做yolov5目标检测的程序。在编写这套程序时,遇到的bug和解决办法,在这篇文章里讲述一下。在yolov5之前的yolov3和yolov4的官方代码都是基于darknet框架的实现的,因此opencv的dnn模块做目标检测时,读取的是.cfg和.weight文件,那时候编写程序很顺畅,没有遇到bug。但是yolo_opencv yolov5