重现

在 .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源码:https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs

Task.Run(), Task.Factory.StartNew() 和 New Task() 的行为不一致分析的更多相关文章

  1. 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(( ...

  2. Task.Run Vs Task.Factory.StartNew

    在.Net 4中,Task.Factory.StartNew是启动一个新Task的首选方法.它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至 ...

  3. Task.Run Vs Task.Factory.StartNew z

    在.Net 4中,Task.Factory.StartNew是启动一个新Task的首选方法.它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至 ...

  4. Task.Run与Task.Factory.StartNew的区别

    Task是可能有延迟的工作单元,目的是生成一个结果值,或产生想要的效果.任务和线程的区别是:任务代表需要执行的作业,而线程代表做这个作业的工作者. 在.Net 4中,Task.Factory.Star ...

  5. C# Task.Run 和 Task.Factory.StartNew 区别

    Task.Run 是在 dotnet framework 4.5 之后才可以使用,但是 Task.Factory.StartNew 可以使用比 Task.Run 更多的参数,可以做到更多的定制.可以认 ...

  6. Task.Run Vs Task.Factory.StartNew 【收藏】

    在.Net 4中,Task.Factory.StartNew是启动一个新Task的首选方法.它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至 ...

  7. 【.NET】- Task.Run 和 Task.Factory.StartNew 区别

    Task.Run 是在 dotnet framework 4.5 之后才可以使用, Task.Factory.StartNew 可以使用比 Task.Run 更多的参数,可以做到更多的定制. 可以认为 ...

  8. Task.Run 和 Task.Factory.StartNew

    在.Net 4中,Task.Factory.StartNew是启动一个新Task的首选方法.它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至 ...

  9. Task.Run 和 Task.Factory.StartNew 区别

    Task.Run 是在 dotnet framework 4.5 之后才可以使用, Task.Factory.StartNew 可以使用比 Task.Run 更多的参数,可以做到更多的定制. 可以认为 ...

随机推荐

  1. Java语言学习day01--6月28日

    Java语言学习day01一:Java概述 1.Java语言发展史 任职于太阳微系统的 詹姆斯·高斯林 等人于1990年代初开发Java语言的雏形,最初被命名为 Oak ,目标设置在 家用电器等小型系 ...

  2. 24张图攻克border-image

    大家好,我是半夏,一个刚刚开始写文的沙雕程序员.如果喜欢我的文章,可以关注 点赞 加我微信:frontendpicker,一起学习交流前端,成为更优秀的工程师-关注公众号:搞前端的半夏,了解更多前端知 ...

  3. WIN10 使用POWERSHELL 设置单应用KIOSK模式(win10家庭版或企业版)

    win10 使用PowerShell 设置单应用kiosk模式 win10 家版或企业版PowerShellshell 启动器 v1Autologon.exe 注意事项 win10 家庭版或企业版. ...

  4. Windows平台安装SQLite3数据库

    Windows平台安装SQLite3数据库 话不多说,开始! 访问SQLite官网下载资源 在搜索引擎中键入SQLite3关键字寻找官网入口或直接点击此处前往SQLite官网,官网界面如下: 点击页面 ...

  5. 2020级cpp机考模拟题A卷-#题解2

    这部分的题目都有一定难度,有兴趣的同学可以钻研一下. 特此感谢来自BDT20030  tql的支持. 2:素数的和-2 题意: 计算不大于m的素数之和.(多么容易理解的题目啊,对吧) 题解(有点复杂的 ...

  6. map计算

    map理解 参考1: https://github.com/rafaelpadilla/Object-Detection-Metrics 参考2:https://github.com/rafaelpa ...

  7. 关于基础RMQ——ST算法

    RMQ,Range Maximum/Minimum Query,顾名思义,就是询问某个区间内的最大值或最小值,今天我主要记录的是其求解方法--ST算法 相对于线段树,它的运行速度会快很多,可以做到O( ...

  8. 基于云服务MRS构建DolphinScheduler2调度系统

    摘要:本文介绍如何搭建DolphinScheduler并运行MRS作业. 本文分享自华为云社区<基于云服务MRS构建DolphinScheduler2调度系统>,作者: 啊喔YeYe . ...

  9. Citrix Virtual Apps and Desktops 7 2203 LTSR虚拟云桌面单机教程

    哈喽大家好,欢迎来到虚拟化时代君(XNHCYL). 大家好,我是虚拟化时代君,一位潜心于互联网的技术宅男.这里每天为你分享各种你感兴趣的技术.教程.软件.资源.福利--(每天更新不间断) 一 .主要内 ...

  10. .NET C#杂谈(1):变体 - 协变、逆变与不变

    0. 文章目的:   介绍变体的概念,并介绍其对C#的意义 1. 阅读基础   了解C#进阶语言功能的使用(尤其是泛型.委托.接口) 2. 从示例入手,理解变体   变体这一概念用于描述存在继承关系的 ...