Task.Run() 和 Task.Factory.StartNew() 有啥区别
Posted
技术标签:
【中文标题】Task.Run() 和 Task.Factory.StartNew() 有啥区别【英文标题】:What is the difference between Task.Run() and Task.Factory.StartNew()Task.Run() 和 Task.Factory.StartNew() 有什么区别 【发布时间】:2016-11-20 06:41:55 【问题描述】:我有方法:
private static void Method()
Console.WriteLine("Method() started");
for (var i = 0; i < 20; i++)
Console.WriteLine("Method() Counter = " + i);
Thread.Sleep(500);
Console.WriteLine("Method() finished");
我想在一个新任务中启动这个方法。 我可以像这样开始新任务
var task = Task.Factory.StartNew(new Action(Method));
或者这个
var task = Task.Run(new Action(Method));
但是Task.Run()
和Task.Factory.StartNew()
之间有什么区别吗?他们都在使用 ThreadPool 并在创建任务实例后立即启动 Method()。什么时候应该使用第一个变体,什么时候应该使用第二个?
【问题讨论】:
其实,StartNew 不必使用 ThreadPool,请参阅我在回答中链接到的博客。问题是StartNew
默认使用TaskScheduler.Current
,它可能是线程池,也可能是 UI 线程。
Regarding usage of Task.Start() , Task.Run() and Task.Factory.StartNew()的可能重复
【参考方案1】:
第二种方法Task.Run
已在更高版本的 .NET 框架(在 .NET 4.5 中)中引入。
但是,第一种方法 Task.Factory.StartNew
让您有机会定义很多关于您要创建的线程的有用信息,而 Task.Run
不提供此功能。
例如,假设您要创建一个长时间运行的任务线程。如果线程池中的某个线程将用于此任务,则可以认为这是对线程池的滥用。
为了避免这种情况,您可以做的一件事是在单独的线程中运行任务。一个新创建的线程将专门用于此任务,一旦您的任务完成就会被销毁。您无法使用Task.Run
实现此目的,但您可以使用Task.Factory.StartNew
执行此操作,如下所示:
Task.Factory.StartNew(..., TaskCreationOptions.LongRunning);
正如here所说:
因此,在 .NET Framework 4.5 开发者预览版中,我们引入了 新的 Task.Run 方法。 这绝不会过时 Task.Factory.StartNew, 但应该简单地认为是一种快速使用的方法 Task.Factory.StartNew 不需要指定一堆 参数。是一个捷径。其实Task.Run其实就是 根据用于 Task.Factory.StartNew 的相同逻辑实现, 只是传入一些默认参数。当您将 Action 传递给 任务运行:
Task.Run(someAction);
这完全等同于:
Task.Factory.StartNew(someAction,
CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
【讨论】:
我有一段代码声明that’s exactly equivalent to
不成立。
@Emaborsa 如果您能发布这段代码并详细说明您的论点,我将不胜感激。提前致谢!
@Emaborsa 你可以创建一个要点,gist.github.com,然后分享它。但是,除了分享这个要点之外,请说明您是如何得出tha's exactly equivalent to
不成立的结果的。提前致谢。最好用对您的代码的评论来解释。谢谢:)
另外值得一提的是Task.Run默认解包嵌套任务。我建议阅读这篇关于主要差异的文章:blogs.msdn.microsoft.com/pfxteam/2011/10/24/…
@The0bserver 不,是TaskScheduler.Default
。请看这里referencesource.microsoft.com/#mscorlib/system/threading/Tasks/…。【参考方案2】:
人们已经提到过
Task.Run(A);
等价于
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
但没有人提及
Task.Factory.StartNew(A);
相当于:
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current);
如您所见,Task.Run
和 Task.Factory.StartNew
的两个参数不同:
TaskCreationOptions
- Task.Run
使用 TaskCreationOptions.DenyChildAttach
这意味着子任务不能附加到父任务,请考虑:
var parentTask = Task.Run(() =>
var childTask = new Task(() =>
Thread.Sleep(10000);
Console.WriteLine("Child task finished.");
, TaskCreationOptions.AttachedToParent);
childTask.Start();
Console.WriteLine("Parent task finished.");
);
parentTask.Wait();
Console.WriteLine("Main thread finished.");
当我们调用parentTask.Wait()
时,childTask
将不会被等待,即使我们为它指定了TaskCreationOptions.AttachedToParent
,这是因为TaskCreationOptions.DenyChildAttach
禁止孩子附加到它。如果您使用Task.Factory.StartNew
而不是Task.Run
运行相同的代码,parentTask.Wait()
将等待childTask
,因为Task.Factory.StartNew
使用TaskCreationOptions.None
TaskScheduler
- Task.Run
使用TaskScheduler.Default
,这意味着默认任务调度程序(在线程池上运行任务的调度程序)将始终用于运行任务。另一方面,Task.Factory.StartNew
使用TaskScheduler.Current
,这意味着当前线程的调度程序,它可能是TaskScheduler.Default
,但并非总是如此。事实上,在开发 Winforms
或 WPF
应用程序时,需要从当前线程更新 UI,为此人们使用 TaskScheduler.FromCurrentSynchronizationContext()
任务调度程序,如果您无意中在使用 TaskScheduler.FromCurrentSynchronizationContext()
调度程序的任务中创建了另一个长时间运行的任务UI 将被冻结。更详细的解释可以看here
所以一般来说,如果你不使用嵌套子任务并且总是希望你的任务在线程池上执行,最好使用Task.Run
,除非你有一些更复杂的场景。
【讨论】:
这是一个很棒的提示,应该是公认的答案 很好,但是即使你有嵌套任务,你也可以使用 Task.Run().Unwrap().ContinueWith() ,对吧?【参考方案3】:请参阅描述差异的this blog article。基本上在做:
Task.Run(A)
和做的一样:
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
【讨论】:
【参考方案4】:Task.Run
是在较新的 .NET 框架版本中引入的,它是 recommended。
从 .NET Framework 4.5 开始,Task.Run 方法是 启动计算绑定任务的推荐方法。使用 StartNew 仅当您需要细粒度控制以进行长时间运行时才使用该方法, 计算绑定任务。
Task.Factory.StartNew
有更多选项,Task.Run
是简写:
Run 方法提供了一组使其易于启动的重载 使用默认值的任务。它是轻量级的替代品 StartNew 重载。
我的速记是指技术shortcut:
public static Task Run(Action action)
return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default,
TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
【讨论】:
【参考方案5】:根据 Stephen Cleary 的这篇文章,Task.Factory.StartNew() 很危险:
我在博客和 SO 问题中看到很多代码,它们使用 Task.Factory.StartNew 在后台线程上启动工作。 Stephen Toub 有一篇优秀的博客文章解释了为什么 Task.Run 比 Task.Factory.StartNew 更好,但我认为很多人只是没有读过它(或者不理解它)。所以,我采用了相同的论点,添加了一些更有力的语言,我们将看看这是怎么回事。 :) StartNew 确实提供了比 Task.Run 更多的选项,但正如我们将看到的那样,它非常危险。在异步代码中,您应该更喜欢 Task.Run 而不是 Task.Factory.StartNew。
以下是实际原因:
不理解异步委托。这实际上是一样的 第 1 点是您想要使用 StartNew 的原因。问题 是当您将异步委托传递给 StartNew 时,很自然 假设返回的任务代表该委托。然而,由于 StartNew 不了解异步委托,该任务实际上是什么 代表只是该代表的开始。这是其中之一 程序员在异步使用 StartNew 时遇到的第一个陷阱 代码。 令人困惑的默认调度程序。 OK,诡计提问时间:在 下面的代码,方法“A”在哪个线程上运行?
Task.Factory.StartNew(A);
private static void A()
嗯,你知道这是一个棘手的问题,嗯?如果你回答“一个线程 池线程”,对不起,但这是不正确的。 “A”将继续运行 无论 TaskScheduler 当前正在执行什么!
这意味着如果操作完成,它可能会在 UI 线程上运行,并且由于继续,它可能会编组回 UI 线程,正如 Stephen Cleary 在他的帖子中更全面地解释的那样。
在我的例子中,我试图在为视图加载数据网格时在后台运行任务,同时还显示繁忙的动画。使用Task.Factory.StartNew()
时没有显示忙碌的动画,但切换到Task.Run()
时可以正常显示动画。
详情请见https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html
【讨论】:
【参考方案6】:除了相似之处,即 Task.Run() 是 Task.Factory.StartNew() 的简写之外,它们在同步委托和异步委托的情况下的行为之间存在细微差别。
假设有以下两种方法:
public async Task<int> GetIntAsync()
return Task.FromResult(1);
public int GetInt()
return 1;
现在考虑下面的代码。
var sync1 = Task.Run(() => GetInt());
var sync2 = Task.Factory.StartNew(() => GetInt());
这里sync1和sync2的类型都是Task<int>
但是,异步方法的情况有所不同。
var async1 = Task.Run(() => GetIntAsync());
var async2 = Task.Factory.StartNew(() => GetIntAsync());
在这种情况下,async1 的类型为 Task<int>
,而 async2 的类型为 Task<Task<int>>
【讨论】:
是的,因为Task.Run
内置了Unwrap
方法的展开功能。 Here 是一篇博文,解释了这一决定背后的原因。以上是关于Task.Run() 和 Task.Factory.StartNew() 有啥区别的主要内容,如果未能解决你的问题,请参考以下文章
Task.Run() 和 Task.Factory.StartNew() 有啥区别
Task.Run 和 Task.Factory.StartNew 区别
关于 Task.Start() 、 Task.Run() 和 Task.Factory.StartNew() 的使用
Task.Factory.StartNew 和 Task.Run