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.RunTask.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,但并非总是如此。事实上,在开发 WinformsWPF 应用程序时,需要从当前线程更新 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&lt;int&gt;

但是,异步方法的情况有所不同。

var async1 = Task.Run(() => GetIntAsync());
var async2 = Task.Factory.StartNew(() => GetIntAsync());

在这种情况下,async1 的类型为 Task&lt;int&gt;,而 async2 的类型为 Task&lt;Task&lt;int&gt;&gt;

【讨论】:

是的,因为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

Task.Factory.StartNew和Task.Run有啥区别

Task.Factory.StartNew<TResult; 和 Task.Run<TResult; 到底有什么区别?