翻译自 Stephen Toub 2011年10月24日的博文《Task.Run vs Task.Factory.StartNew》,Stephen Toub 是微软并行计算平台团队的首席架构师。

.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

  1. Task.Run(someAction);

完全等同于:

  1. 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,这将有助于突出这一区别。如果我编写下面的调用:

  1. var t = Task.Factory.StartNew(() =>
  2. {
  3. Task inner =Task.Factory.StartNew(() => {});
  4. return inner;
  5. });

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

  1. var t = Task.Factory.StartNew(() =>
  2. {
  3. Task<int> inner = Task.Factory.StartNew(() => 42));
  4. return inner;
  5. });

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

  1. var t = Task.Factory.StartNew(async delegate
  2. {
  3. await Task.Delay(1000);
  4. return 42;
  5. });

这里通过使用 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),我可以编写:

  1. var t = Task.Factory.StartNew(async delegate
  2. {
  3. await Task.Delay(1000);
  4. return 42;
  5. }).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)操作。所以,当我写下:

  1. var t = Task.Run(async delegate
  2. {
  3. await Task.Delay(1000);
  4. return 42;
  5. });

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

  1. var t = Task.Factory.StartNew(async delegate
  2. {
  3. await Task.Delay(1000);
  4. return 42;
  5. }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();

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

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

  1. int result = await Task.Run(async () =>
  2. {
  3. await Task.Delay(1000);
  4. return 42;
  5. });

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

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

  1. int result = await Task.Factory.StartNew(async delegate
  2. {
  3. await Task.Delay(1000);
  4. return 42;
  5. }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();

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

  1. int result = await await Task.Factory.StartNew(async delegate
  2. {
  3. await Task.Delay(1000);
  4. return 42;
  5. }, 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

译者 : 技术译民

出品 : 技术译站

链接 : 英文原文

.NET - Task.Run vs Task.Factory.StartNew的更多相关文章

  1. Task.Run Vs Task.Factory.StartNew

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

  2. Task.Run Vs Task.Factory.StartNew z

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

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

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

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

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

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

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

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

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

  7. Task.Run 和 Task.Factory.StartNew

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

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

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

  9. .Net4.0如何实现.NET4.5中的Task.Run及Task.Delay方法

    前言 .NET4.0下是没有Task.Run及Task.Delay方法的,而.NET4.5已经实现,对于还在使用.NET4.0的同学来说,如何在.NET4.0下实现这两个方法呢? 在.NET4.0下, ...

随机推荐

  1. Python os.close() 方法

    概述 os.close() 方法用于关闭指定的文件描述符 fd.高佣联盟 www.cgewang.com 语法 close()方法语法格式如下: os.close(fd); 参数 fd -- 文件描述 ...

  2. 网络滴神,TCP!

     TCP在网络协议(网络协议见这篇文章)中的重要性就相当于女朋友对于程序员的重要一样,这么说你应该知道有多重要了吧. 1. 三次握手 TCP在进行数据的传输之前必须先建立连接,建立之后才能进行数据的传 ...

  3. luogu 2478 [SDOI2010]城市规划 仙人掌上dp.

    LINK:城市规划 以前ls 让写的时候由于看不懂题目+以为在图中的环上dp非常困难所以放弃治疗了. 现在终于能把题目看懂了 泪目... 题目其实就是在说 给出一张图这个有一个非常好的性质 满足每个点 ...

  4. 强烈推荐的 IntelliJ IDEA 插件,别说我没告诉你

    为什么你的 Intellij IDEA 没别人的好用?还不是因为你缺少这几个插件啊! 善用 Intellij IDEA 插件可以提高我们的开发效率,今天和大家一起分享一下实际工作中常用的几款能提升幸福 ...

  5. 【CSP2019】括号树 题解(递推+链表)

    前言:抽时间做了做这道题,把学长送退役的题. ----------------- 题目链接 题目大意:定义$()$是合法括号串.如果$A,B$是合法括号串,那么$(AB),AB$为合法括号串.现给定根 ...

  6. 为什么overflow:hidden能达到清除浮动的目的?

    1. 什么是浮动 <精通CSS>(第3版)关于浮动的描述: 浮动盒子可以向左或向右移动,直到其外边沿接触包含块的外边沿,或接触另一个浮动盒子的外边沿. 浮动盒子也会脱离常规文档流,因此常规 ...

  7. Windows下使用命令行编译Qt项目(解决DLL丢失问题)

    一.前言 我之前用Qt做了个hello world,结果各种报错,一大堆DLL找不到,今天用命令行编译就通过了 二.准备工作 1.Visual Studio(有nmake就行) 2.Qt 3.把qma ...

  8. 百度小程序中swan.setPageInfo的用法

    现在百度智能小程序是百度最新的流量入口,现在很多做SEO优化.小程序开发的企业为了获取更多的流量不得不开发了,很多的技术人员不了解百度小程序的标题和关键词.描述等信息不知道在哪里设置. 以下是小编给你 ...

  9. 字节跳动:[编程题]万万没想到之聪明的编辑 Java

    时间限制:1秒 空间限制:32768K 我叫王大锤,是一家出版社的编辑.我负责校对投稿来的英文稿件,这份工作非常烦人,因为每天都要去修正无数的拼写错误.但是,优秀的人总能在平凡的工作中发现真理.我发现 ...

  10. java System类、Math类、Arrays类

    一 System类 1.概念 在API中System类介绍的比较简单,我们给出定义,System中代表程序所在系统,提供了对应 的一些系统属性信息,和系统操作. System类不能手动创建对象,因为构 ...