Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >.NET - Task.Run vs Task.Factory.StartNew

.NET - Task.Run vs Task.Factory.StartNew

作者头像
独立观察员
发布于 2022-12-06 10:26:45
发布于 2022-12-06 10:26:45
42900
代码可运行
举报
运行总次数:0
代码可运行

翻译自 Stephen Toub 2011年10月24日的博文《Task.Run vs Task.Factory.StartNew》,Stephen Toub 是微软 .NET 团队的一名开发人员。

.NET 4 中,Task.Factory.StartNew 是安排新任务的首选方法。它有许多重载提供了高度可配置的机制,通过启用设置选项,可以传递任意状态、启用取消,甚至控制调度行为。所有这些功能的另一面是复杂性。您需要知道什么时候使用哪个重载、提供什么调度程序等等。另外,Task.Factory.StartNew 用起来并不直截干脆,至少对于它的一些使用场景来说还不够快,比如它的主要使用场景——轻松地将工作交付到后台处理线程。

因此,在 .NET Framework 4.5 开发者预览版 中,我们引入了新的 Task.Run 方法。这决不是要淘汰 Task.Factory.StartNew,而是应该简单地认为这是使用 Task.Factory.StartNew 而不必传递一堆参数的一个便捷方式。这是一个捷径。事实上,Task.Run 实际是按照与 Task.Factory.StartNew 相同的逻辑实现的,只是传入了一些默认的参数。当你传递一个 ActionTask.Run

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Task.Run(someAction);

完全等同于:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

通过这种方式,Task.Run 就可以并且应该被用于大多数通用场景——简单地将工作交给线程池ThreadPool处理(即参数 TaskScheduler.Default 的目标)。这并不意味着 Task.Factory.StartNew 将不再被使用; 远非如此,Task.Factory.StartNew 还有很多重要的(固然更高级)用途。你可以控制 TaskCreationOptions 来控制任务的行为,可以控制 TaskScheduler 来控制任务的调度和运行,也可以使用接收对象状态的重载,对于性能敏感的代码路径,使用该重载可以避免闭包和相应的内存分配。不过,对于简单的情况,Task.Run 是你的朋友。

Task.Run 提供八个重载,以支持下面的所有组合:

  1. 无返回值任务(Task)和有返回值任务(Task<TResult>)
  2. 支持取消(cancelable)和不支持取消(non-cancelable)
  3. 同步委托(synchronous delegate)和异步委托(asynchronous delegate)

前两点应该是不言而喻的。对于第一点,有返回 Task 的重载(对于没有返回值的操作),还有返回 Task<TResult> 的重载(对于返回值类型为 TResult 的操作)。对于第二点,还有接受 CancellationToken 的重载,如果在任务开始执行之前请求取消,则任务并行库(TPL)可以将任务转换为取消状态。

第三点是更有趣的,它与 Visual Studio 11 中 C# 和 Visual Basic 的异步语言支持直接相关。让我们暂时考虑一下 Task.Factory.StartNew,这将有助于突出这一区别。如果我编写下面的调用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var t = Task.Factory.StartNew(() =>
{
    Task inner =Task.Factory.StartNew(() => {});
    return inner;
});

这里的 “t” 的类型将会是 Task<Task>; 因为任务委托的类型是 Func<TResult>,在此例中 TResultTask,因此 StartNew 的返回值是 Task<Task>。类似地,如果我将代码改变为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var t = Task.Factory.StartNew(() => 
{
    Task<int> inner = Task.Factory.StartNew(() => 42));
    return inner;
});

此时,这里的 “t” 的类型将会是 Task<Task<int>>。因为任务委托的类型是 Func<TResult>,此时 TResultTask<int>,因此 StartNew 的返回值是 Task<Task<int>>。为什么这是相关的?现在考虑一下,如果我编写如下的代码会发生什么:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var t = Task.Factory.StartNew(async delegate
{
    await Task.Delay(1000);
    return 42;
});

这里通过使用 async 关键词,编译器会将这个委托(delegate)映射成 Func<Task<int>>,调用该委托会返回 Task<int> 表示此调用的最终完成。因为委托是 Func<Task<int>>TResultTask<int>,因此这里 “t” 的类型将会是 Task<Task<int>>,而不是 Task<int>

为了处理这类情况,在 .NET 4 我们引入了 Unwrap 方法。Unwrap 方法有两个重载,都是扩展方法,一个针对类型 Task<Task>,一个针对类型 Task<Task<TResult>>。我们称此方法为 Unwrap,因为实际上它“解包”了内部任务,将内部任务的返回值作为了外部任务的返回值而返回。对 Task<Task> 调用 Unwrap 返回一个新的 Task(我们通常将其称为代理),它表示该内部任务的最终完成。类似地,对 Task<Task<TResult>> 调用 Unwrap 返回一个新的 Task<TResult> 表示该内部任务的最终完成。(在这两种情况下,如果外部任务出错或被取消,则不存在内部任务,因为没有运行到完成的任务不会产生结果,因此代理任务表示外部任务的状态。) 回到前面的例子,如果我希望 “t” 表示那个内部任务的返回值(在此例中,值是 42),我可以编写:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var t = Task.Factory.StartNew(async delegate
{
    await Task.Delay(1000);
    return 42;
}).Unwrap();

现在,这里 “t” 变量的类型将会是 Task<int>,表示异步调用的返回值。

回到 Task.Run。因为我们希望人们将工作转移到线程池(ThreadPool)中并使用 async/await 成为普遍现象,所以我们决定将此解包(unwrapping)功能构建到 Task.Run 中。这就是上面第三点中提到的内容。有多种 Task.Run 的重载,它们接受 Action(针对无返回值任务)、 Func<TResult>(针对返回 TResult 的任务)、Func<Task>(针对无返回值的异步任务) 和 Func<Task<TResult>>(针对返回 TResult 的异步任务)。在内部,Task.Run 会执行与上面 Task.Factory.StartNew 所示的同样类型的解包(unwrapping)操作。所以,当我写下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var t = Task.Run(async delegate
{
    await Task.Delay(1000);
    return 42;
});

“t” 的类型是 Task<int>Task.Run 的这种重载实现基本上等效于:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var t = Task.Factory.StartNew(async delegate
{
    await Task.Delay(1000);
    return 42;
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();

如前所述,这是一条捷径。

所有这些都意味着您可以将 Task.Run 与常规lambdas/匿名方法或与异步lambdas/匿名方法一起使用,都会发生正确的事情。如果我想将工作交给线程池(ThreadPool)并等待其结果,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int result = await Task.Run(async () =>
{
    await Task.Delay(1000);
    return 42;
});

变量 result 的类型将会是 int,正如您期望的那样,在调用此任务大约一秒种后,变量 result 的值将被设置为 42。

有趣的是,几乎可以将新的 await 关键字看作是与 Unwrap 方法等效的语言。因此,如果我们返回到 Task.Factory.StartNew 示例,则可以使用 Unwrap 重写上面最后一个代码片断,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int result = await Task.Factory.StartNew(async delegate
{
    await Task.Delay(1000);
    return 42;
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();

或者,我可以使用第二个 await 来代替使用 Unwrap

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int result = await await Task.Factory.StartNew(async delegate
{
    await Task.Delay(1000);
    return 42;
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

这里的 “await await” 不是输入错误,Task.Factory.StartNew 返回 Task<Task<int>>await Task<Task<int>> 返回 Task<int>,然后 await Task<int> 返回 int,很有趣,对吧?

作者 :Stephen Toub 译者 :技术译民 出品 :技术译站(ID:ITTranslator)

END

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 独立观察员博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
理解Task和和async await
本文将详解C#类当中的Task,以及异步函数async await和Task的关系
ryzenWzd
2020/11/12
2.3K0
理解Task和和async await
这样在 C# 使用 LongRunningTask 是错的
Task.Factory.StartNew 有一个重载,是支持 TaskCreationOptions.LongRunning 参数来指定 Task 的特征的。但是可能在没有注意的情况下,你就使用了错误的用法。那么本文我们来简单阐述一下这个参数的作用,和使用的注意要点。
newbe36524
2023/08/23
4910
Task.Run 和 Task.Factory.StartNew 区别——c#
Task.Run 是在 dotnet framework 4.5 之后才可以使用, Task.Factory.StartNew 可以使用比 Task.Run 更多的参数,可以做到更多的定制。
vv彭
2021/01/06
1.4K0
面试八股文:你写过自定义任务调度器吗?
官方推荐使用Task.Run方法启动基于计算的任务, 当需要对长时间运行、基于计算的任务做精细化控制时使用Task.Factory.StartNew。
有态度的马甲
2021/05/07
7290
.Net多线程编程—任务Task
1 System.Threading.Tasks.Task简介 一个Task表示一个异步操作,Task的创建和执行是独立的。 只读属性: 返回值 名称 说明 object AsyncState 表示在创建任务时传递给该任务的状态数据 TaskCreationOptions CreationOptions 获取用于创建此任务的 TaskCreationOptions CurrentId 当前正在执行 Task 的 ID
甜橙很酸
2018/03/08
1.6K0
.Net4.0如何实现.NET4.5中的Task.Run及Task.Delay方法
本文介绍了在.NET 4.0中,如何使用Task.Run、Task.Delay等方法来异步地执行一些方法。同时,也介绍了一些其他的异步方法,如异步的WebRequest等。通过这些方法,可以让我们在.NET 4.0中异步地执行一些操作,从而提高应用程序的性能和响应速度。
CNXY
2017/12/25
1.9K0
[C#] Task.CompletedTask和Task.Result什么时候用?
在学习C#中的Task方法时,可以知道Task启动一个异步线程方法可以用Task.Run()进行,具体可以参看附录部分。
科控物联
2023/09/01
2.2K0
[C#] Task.CompletedTask和Task.Result什么时候用?
C#/.NET 中 Thread.Sleep(0), Task.Delay(0), Thread.Yield(), Task.Yield() 不同的执行效果和用法建议
在 C#/.NET 中,有 Thread.Sleep(0), Task.Delay(0), Thread.Yield(), Task.Yield() 中,有几种不同的让当前线程释放执行权的方法。他们的作用都是放弃当前线程当前的执行权,让其他线程得以调度。但是他们又不太一样。
walterlv
2023/10/22
1.5K0
C#/.NET 中 Thread.Sleep(0), Task.Delay(0), Thread.Yield(), Task.Yield() 不同的执行效果和用法建议
c#异步编程-Task(一)
大家好,本次继续分享自己的学习经历。本文主要分享异步编程中Task的使用,如果能帮助大家希望多多关注文章末尾的微信公众号和知乎三连。各位举手之劳是对我更新技术文章最大的支持。
JusterZhu
2022/12/07
7140
C# Task.Run 和 Task.Factory.StartNew 区别 创建新线程等待线程长时间运行
有小伙伴问我,为什么不推荐他使用 Task.Factory.StartNew ,因为 Task.Run 是比较新的方法。 本文告诉大家 Task.Run 和 Task.Factory.StartNew 区别
林德熙
2018/09/18
5.5K0
C#多线程(13):任务基础①
.NET 中,有三种异步编程模式,分别是基于任务的异步模式(TAP)、基于事件的异步模式(EAP)、异步编程模式(APM)。
痴者工良
2021/04/26
9440
c# 委托(Func、Action)
以前自己写委托都用 delegate, 最近看组里的大佬们都用 Func , 以及 Action 来实现, 代码简洁了不少, 但是看得我晕晕乎乎。 花点时间研究一下,记录一下,以便后期的查阅。
用户2434869
2018/09/12
7.4K0
Asp.Net Core 轻松学-多线程之Task快速上手
    Task是从 .NET Framework 4 开始引入的一项基于队列的异步任务(TAP)模式,从 .NET Framework 4.5 开始,任何使用 async/await 进行修饰的方法,都会被认为是一个异步方法;实际上,这些异步方法都是基于队列的线程任务,从你开始使用 Task 去运行一段代码的时候,实际上就相当于开启了一个线程,默认情况下,这个线程数由线程池 ThreadPool 进行管理的。
梁规晓
2019/04/11
1.6K0
Asp.Net Core 轻松学-多线程之Task快速上手
.NET系列走进Task:Task的回调执行与await
上一篇我们讲了对 Task 的基本定义:Task 代表一个任务,其具体类型可能是多种多样的,且有时候对我们来说完全是个黑盒。这个任务可以有结果,可以没有结果,我们能知道这个任务什么时候执行完成,并进行相应的后续处理。
郑子铭
2022/03/22
2.7K0
.NET系列走进Task:Task的回调执行与await
C# 多线程六之Task(任务)三之任务工厂
前面两篇关于Task的随笔,C# 多线程五之Task(任务)一 和 C# 多线程六之Task(任务)二,介绍了关于Task的一些基本的用法,以及一些使用的要点,如果都看懂了,本文将介绍另一个Task的特殊用法,前面介绍了,如何通过一个父任务创建多个子任务,且这些子任务都必须要支持取消的例子,常规做法是,通过new 一个Task数组对象,然后在该对象的内部创建多个Task任务,然后给这些任务指定TaskCreationOptions.AttachedToParent,这样所有的子任务都关联到了父任务,接着给这些子任务,绑定一个CancellationToken类实例,当其中一个子任务发生异常时,调用CancellationToken类实例的Cancel方法,将其余的子任务全都取消,大致代码如下:
郑小超.
2018/12/24
9530
C#异步使用要点(翻译)
在使用异步方法中最好不要使用void当做返回值,无返回值也应使用Task作为返回值,因为使用void作为返回值具有以下缺点
莫问今朝
2018/11/05
3.4K0
实现常驻任务除了避免昙花线程,还需要避免重返线程池
前面我们使用简单的例子演示了 Task 和 Thread 的两种制造昙花线程的方式。那么除了避免昙花线程,在实现常驻任务的时候,还需要避免重返线程池。本文将介绍如何避免重返线程池。
newbe36524
2023/03/20
3000
实现常驻任务除了避免昙花线程,还需要避免重返线程池
c#异步编程-Task(二)
大家好,本次继续分享自己的学习经历。本文主要分享Task异步编程内容,如果能帮助大家希望多多关注文章末尾的微信公众号和知乎三连。各位举手之劳是对我更新技术文章最大的支持。
JusterZhu
2022/12/07
2.6K0
c#异步编程-Task(二)
【深入浅出C#】章节 9: C#高级主题:多线程编程和并发处理
多线程编程和并发处理的重要性和背景 在计算机科学领域,多线程编程和并发处理是一种关键技术,旨在充分利用现代计算机系统中的多核处理器和多任务能力。随着计算机硬件的发展,单一的中央处理单元(CPU)已经不再是主流,取而代之的是多核处理器,这使得同时执行多个任务成为可能。多线程编程允许开发人员将一个程序拆分成多个线程,这些线程可以并行执行,从而提高程序的性能和响应速度。 为什么多线程在现代应用中至关重要?
喵叔
2023/08/26
4.8K0
C#的Task 和 Task<T>
在C#中,Task和Task<T>是实现异步编程的核心类型。它们允许开发者编写非阻塞代码,从而提高应用程序的响应性和吞吐量。本文将深入探讨C#中的Task和Task<T>,包括它们的基本概念、实现方式、高级用法和最佳实践。
Michel_Rolle
2024/10/08
2.5K0
相关推荐
理解Task和和async await
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文