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 更多的参数,可以做到更多的定制. 可以认为 ...
随机推荐
- JavaScript学习①
# 今日内容 1. JavaScript基础 ## JavaScript: * 概念: 一门客户端脚本语言 * 运行在客户端浏览器中的.每一个浏览器都有JavaScript的解析引擎 * 脚本语言:不 ...
- Primitive Primes - 题解【数学】
题面 It is Professor R's last class of his teaching career. Every time Professor R taught a class, he ...
- 源码解析Synchronous Queue 这种特立独行的队列
摘要:Synchronous Queue 是一种特立独行的队列,其本身是没有容量的,比如调用者放一个数据到队列中,调用者是不能够立马返回的,调用者必须等待别人把我放进去的数据消费掉了,才能够返回. 本 ...
- 【干货】BIOS、UEFI、MBR、GPT、GRUB 到底是什么意思?
公众号关注 「开源Linux」 回复「学习」,有我为您特别筛选的学习资料~ 01 前言 在学习 Linux 系统启动原理之前,我们先了解下与操作系统启动相关的几个概念. 02 与操作系统启动相关的几个 ...
- Blazor和Vue对比学习(基础1.5):双向绑定
这章我们来学习,现代前端框架中最精彩的一部分,双向绑定.除了掌握原生HTML标签的双向绑定使用,我们还要在一个自定义的组件上,手撸实现双向绑定.双向绑定,是前两章知识点的一个综合运用(父传子.子传父) ...
- Linux中文件/文件系统的压缩、打包和备份总结(基于rhel7)
文件/文件系统的压缩.打包 Linux有哪些压缩工具可供选择 按压缩比:xz>bzip2>gzip,按压缩时长:gzip>bzip2>xz,另外还有zip可以选择. gzip只 ...
- 认识并安装WSL
认识并安装WSL(基于Windows的Linux子系统) 什么是WSL WSL(Windows Subsystem for Linux),这是在windows平台运行的linux子系统.也就是说可是不 ...
- k8s client-go源码分析 informer源码分析(4)-DeltaFIFO源码分析
client-go之DeltaFIFO源码分析 1.DeltaFIFO概述 先从名字上来看,DeltaFIFO,首先它是一个FIFO,也就是一个先进先出的队列,而Delta代表变化的资源对象,其包含资 ...
- Object类和对象类型转换
学习内容:Object类和对象类型转换 一.Object类 1.Object类是所有类的父类,是Java类层中最高层的类. 2.getClass()方法:返回对象执行时的Class实例,然后用此实例调 ...
- Java概论——JavaSE基础
Java概论 Java特性和优势 简单性 面向对象 可移植性 高性能:即时编译 分布式:可处理TCP/IP协议的一些东西 动态性:通过反射机制使其具有动态性 多线程:良好的交互性和实时性 安全性:防病 ...