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 更多的参数,可以做到更多的定制. 可以认为 ...
随机推荐
- 2021.08.05 P2168 荷马史诗(哈夫曼树模板)
2021.08.05 P2168 荷马史诗(哈夫曼树模板) [P2168 NOI2015] 荷马史诗 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 重点: 1.k叉哈夫曼树如果子结 ...
- 【面试普通人VS高手系列】什么叫做阻塞队列的有界和无界
昨天一个3年Java经验的小伙伴私信我,他说现在面试怎么这么难啊! 我只是面试一个业务开发,他们竟然问我: 什么叫阻塞队列的有界和无界.现在面试也太卷了吧! 如果你也遇到过类似问题,那我们来看看普通人 ...
- Hyperledger Fabric 2.2 学习笔记:测试网络test-network
写在前面 最近被Hyperledger Fabric折磨,归根结底还是因为自己太菜了qwq.学习路漫漫,笔记不能少.下面的步骤均是基于已经成功搭建了Fabric2.2环境,并且拉取fabric-sam ...
- FreeRTOS --(17)任务通知浅析
转载自https://blog.csdn.net/zhoutaopower/article/details/107467305 在 FreeRTOS 中,还有一个东西也可以用作任务与任务,中断与任务的 ...
- JS 中 对象 基础认识
俗话说:"万物皆对象",在 Javascript 中除了原始值几乎所有的东西都可以看做对象: 布尔是对象( new 关键词定义) 数字是对象( new 关键词定义) 字符串是对象 ...
- Android添加布局和按键
Android添加布局和按键 Android布局方式分为 1.LinearLayout (线性布局) 2.ConstraintLayout (约束布局) 3.FrameLayout (帧布局) 4.T ...
- 消息队列,IPC机制(进程间通信),生产者消费者模型,线程及相关
消息队列 创建 ''' Queue是模块multiprocessing中的一个类我们也可以这样导入from multiprocessing import Queue,创 建时queue = Queue ...
- Linux嵌套目录权限的比较探究
在/tmp目录下新建一个嵌套目录,名字分别为test_0.test_1.test_2.在test_2目录下新建普通文件,名为tryme.设置test_0和test_2的权限为777,设置test_1的 ...
- 从0到1使用kubebuiler开发operator
介绍 假设一个Nginx的QPS(服务器一秒内处理的请求数)上限为500,如果外部访问的QPS达到了600,为了保证服务质量,必须扩容一个Nginx来分摊请求. 在Kubernetes环境中,如果外部 ...
- vue中使用echarts的两种方法
在vue中使用echarts有两种方法一.第一种方法1.通过npm获取echarts npm install echarts --save 2.在vue项目中引入echarts 在 main.js 中 ...