重现

在 .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语言学习day08--7月7日

    ###13遍历数组​ * A:遍历数组​ * 在操作数组时,经常需要依次访问数组中的每个元素,这种操作称作数组的遍历​ * B:练习​ public class ArrayDemo04 {​ publ ...

  2. Revit二次开发之创建风管

      在Revit中,风管用于连接管件,风道末端和机械设备,今天简单尝试了下使用RevitAPI创建风管,现分享下我的方法.   风管从类型上可分为三类:一般风管,软风管和风管占位符:从形状上也分为三类 ...

  3. ValidForm5.3.2 忽略表单项校验详解

    ValidForm 官方文档 项目的需求是这样的:一个checkbox,一个input,选中checkbox的时候,需要校验input,取消选中的时候,不要校验input. <input typ ...

  4. XCTF练习题---MISC---Cephalopod

    XCTF练习题---MISC---Cephalopod flag:HITB{95700d8aefdc1648b90a92f3a8460a2c} 解题步骤: 1.观察题目,下载附件 2.拿到手以后发现是 ...

  5. VUE3 之 Teleport - 这个系列的教程通俗易懂,适合新手

    1. 概述 老话说的好:宰相肚里能撑船,但凡成功的人,都有一种博大的胸怀. 言归正传,今天我们来聊聊 VUE 中 Teleport 的使用. 2. Teleport 2.1 遮罩效果的实现  < ...

  6. mysql 主从数据同步配置

    一主一从,单向同步 master 数据库的数据变更单向同步到 slave 数据库 互为主从,双向同步 master 数据库的数据变更同步到 slave 数据库,slave 数据库的数据边同步到 mas ...

  7. 使用Spring MVC开发RESTful API

    第3章 使用Spring MVC开发RESTful API Restful简介 第一印象 左侧是传统写法,右侧是RESTful写法 用url描述资源,而不是行为 用http方法描述行为,使用http状 ...

  8. 824. Goat Latin - LeetCode

    Questioin 824. Goat Latin Solution 题目大意:根据要求翻译句子 思路:转换成单词数组,遍历数组,根据要求转换单词 Java实现: 用Java8的流实现,效率太低 pu ...

  9. CentOS6安装PLEX

    CentOS6安装PLEX 一.安装使用PLEX 1.1 安装 1)下载 https://www.plex.tv/downloads/ plexmediaserver-1.11.3.4803-c40b ...

  10. linux篇-linux awstats搭建

    1安装lnmp模式 2安装awstats 2# tar xf awstats-6.5.tar.gz 解压下载到/root下的压缩包 mkdir /var/lib/awstats chmod 755 / ...