Task.Run(), Task.Factory.StartNew() 和 New Task() 的行为不一致分析
重现
在 .Net5 平台下,创建一个控制台程序,注意控制台程序的Main()方法如下:
static async Task Main(string[] args)
方法的主体非常简单,使用Task.Run创建一个立即执行的Task,在其内部不断输出线程id,直到手动关闭程序,代码如下:
代码片段1
点击查看代码
static async Task Main(string[] args)
{
Console.WriteLine("主线程线程id:" + Thread.CurrentThread.ManagedThreadId);
await Task.Run(async () =>
{
while (true)
{
Console.WriteLine("Fuck World! 线程id:" + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(2000);
Console.WriteLine("线程id:" + Thread.CurrentThread.ManagedThreadId);
}
});
}
这段代码如期运行,并且不需要在程序末尾使用Console.ReadLine()控制程序不停止。
但是如果我们使用Task.Factory.StartNew()替换Task.Run()的话,程序就会一闪而过,立即退出。
如果使用New Task()创建的话,如下代码所示:
代码片段2
点击查看代码
var t = new Task(async () =>
{
while (true)
{
Console.WriteLine("Fuck World!线程:" + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(2000);
Console.WriteLine("线程id:" + Thread.CurrentThread.ManagedThreadId);
}
});
t.Start();
await t;
程序依然一闪而过,立即退出。
分析
首先分析下 Task.Run()和Task.Factory.StartNew()。
我们将 async 标记的λ表达式当作参数传入后,编译器会将λ表达式映射为Func<Task>或者Func<Task<TResult>>委托,本示例中因为没有返回值,所以映射为Func<Task>。
如果我们用F12考察 Task.Run()和Task.Factory.StartNew()在入参为Func<TResult>的情况下的返回值类型的话,会发现他们两者的返回类型都是Task<TResult>。但是在示例中,你会发现,返回值是不一样的。Task.Run(async () ...) 的返回类型是Task,而Task.Factory.StartNew(async () ...) 的返回类型是Task<Task>。
所以,我们在 await Task.Factory.StartNew(async () ...) 的时候,其实是在await Task<Task>, 其结果,依然是一个Task。既然如此,想达到和await Task.Run(async () ...)的效果就非常简单了,只需要再加一个await,即await await Task.Factory.StartNew(async () ...)。读者可自行尝试。
这两个方法行为的差异,可以从源码中找到原因:

Task.Run的内部进行了Unwrap,把Task<Task>外层的Task拆掉了。UnWrap()方法是存在的,可以直接调用,即Task.Factory.StartNew(async () ...).Unwrap(),调用后的结果就是Task。所以await await Task.Factory.StartNew(async () ...)与await Task.Factory.StartNew(async () ...).Unwrap()的结果是一致的。在这一点上,Unwrap()的作用与await的作用一样。
也即:await Task.Run(async () ...) == await await Task.Factory.StartNew(async () ...) == await Task.Factory.StartNew(async () ...).Unwrap()。
接下来考察下New Task()的形式。在代码片段2中,虽然调用了await t,但是代码并没有如期运行,而是一闪而过,程序退出。其实,传入的参数虽然与之前的一致,但是编译器并没有把参数映射为Func<Task>,而是映射为了Action(),也就是并没有返回值。t.Start()的结果,就是让那个Action()开始执行,随后,Task 执行完毕,await t也就瞬间完成,没有任何结果——因为Action()是没有返回值的。在这段代码当中,Action()其实运行在一个后台线程中,如果在主线程上使用Thread.Sleep(10000)后,会发现控制台一直在输出内容。
如果想要以New的方式创建Task的实例实现同样的输出效果,做一下小的改动就可以了,如下所示:
代码片段3
点击查看代码
var t = new Task<Task>(async () =>
{
while (true)
{
Console.WriteLine("Fuck World!线程:" + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(2000);
Console.WriteLine("线程id:" + Thread.CurrentThread.ManagedThreadId);
}
});
t.Start();
await await t;
将New Task(async ()...)改为Nwe Task<Task>(async ()...)就可以了,这样λ表达式async ()...就会映射为Func<Task>,满足了我们异步的需求。
参考
Task.Run(), Task.Factory.StartNew() 和 New Task() 的行为不一致分析的更多相关文章
- C# Task,new Task().Start(),Task.Run();TTask.Factory.StartNew
1. Task task = new Task(() => { MultiplyMethod(a, b); }); task.Start(); 2. Task task = Task.Run(( ...
- Task.Run Vs Task.Factory.StartNew
在.Net 4中,Task.Factory.StartNew是启动一个新Task的首选方法.它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至 ...
- Task.Run Vs Task.Factory.StartNew z
在.Net 4中,Task.Factory.StartNew是启动一个新Task的首选方法.它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至 ...
- Task.Run与Task.Factory.StartNew的区别
Task是可能有延迟的工作单元,目的是生成一个结果值,或产生想要的效果.任务和线程的区别是:任务代表需要执行的作业,而线程代表做这个作业的工作者. 在.Net 4中,Task.Factory.Star ...
- C# Task.Run 和 Task.Factory.StartNew 区别
Task.Run 是在 dotnet framework 4.5 之后才可以使用,但是 Task.Factory.StartNew 可以使用比 Task.Run 更多的参数,可以做到更多的定制.可以认 ...
- Task.Run Vs Task.Factory.StartNew 【收藏】
在.Net 4中,Task.Factory.StartNew是启动一个新Task的首选方法.它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至 ...
- 【.NET】- Task.Run 和 Task.Factory.StartNew 区别
Task.Run 是在 dotnet framework 4.5 之后才可以使用, Task.Factory.StartNew 可以使用比 Task.Run 更多的参数,可以做到更多的定制. 可以认为 ...
- Task.Run 和 Task.Factory.StartNew
在.Net 4中,Task.Factory.StartNew是启动一个新Task的首选方法.它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至 ...
- Task.Run 和 Task.Factory.StartNew 区别
Task.Run 是在 dotnet framework 4.5 之后才可以使用, Task.Factory.StartNew 可以使用比 Task.Run 更多的参数,可以做到更多的定制. 可以认为 ...
随机推荐
- 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 ... 基于. ...
- 你不知道的 Linux 使用技巧
开源Linux 一个执着于技术的公众号 1.快速跳转命令 - z 要是每次都要进入一个目录很深的文件夹下,像下面这样: # cd /root/py/auto/fabric 每次都要输入好多个目录名是不 ...
- 《Mybatis 手撸专栏》第8章:把反射用到出神入化
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 为什么,读不懂框架源码? 我们都知道作为一个程序员,如果想学习到更深层次的技术,就需 ...
- maven install resources failed: newPosition < 0: (-1 < 0)
添加以下代码在 pom.xml 中,具体参阅这里 <build> <plugins> <plugin> <groupId>org.apache.mave ...
- 『忘了再学』Shell基础 — 17、预定义变量
目录 1.预定义变量$? 2.预定义变量$$和$! 我们之前说过,Shell中的变量不是按照变量值的类型来进行分类的,而是按照Linux系统中定义的变量类别来分类的. 预定义变量就是,事先把变量的名称 ...
- 利用SignalR创建即时消息
1. 什么是SignalR? SignalR 是一个及时消息推送,它与.NET 的 WCF ,WebAPI类似 是客户端和服务器进行消息交换的一种工具 2.SignalR 的作用? 它可以实时同步在线 ...
- 网易数帆 Envoy Gateway 实践之旅:坚守 6 年,峥嵘渐显
服务网格成熟度不断提升,云原生环境下流量处理愈发重要, Envoy Gateway 项目于近日宣布开源,"旨在大幅降低将 Envoy 作为 API 网关的使用门槛",引发了业界关注 ...
- CSS元素的几种显示模式
元素的显示模式 元素的显示模式就是元素以生么方式进行显示,比如<div>自己占一行,比如一行可以放多个<span>. HTML元素一般分为块元素和行内元素. 块元素 常见的块元 ...
- STM32时钟系统配置程序源码深入分析
一.分析程序的目的 最近我在移植实时系统是遇到了一些问题,所以决定深入了解系统时钟的配置过程,当然想要学好stm32的小伙伴也有必要学习好时钟系统的配置,所以我将学习的过程再次记录,有写得不好的地方, ...
- csv.reader(f)和f.readlines()、追加数据
假如某个文档f中存储如下内容: 你好,中国. 1,2,3,4 共两行内容. 当你使用csv.reader(f),则会存储为如下形式: [['你','好','中','国'] ['1','2','3',' ...