Task.Run()和Task.Factory.StartNew()之间有什么区别

时间:2016-07-17 16:34:48

标签: c# multithreading task-parallel-library

我有方法:

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");
}

我想在一个新的Task中启动这个方法。 我可以像这样开始新任务

var task = Task.Factory.StartNew(new Action(Method));

或者

var task = Task.Run(new Action(Method));

Task.Run()Task.Factory.StartNew()之间是否有任何区别。它们都使用ThreadPool并在创建Task的实例后立即启动Method()。当我们应该使用第一个变体和第二个时?

7 个答案:

答案 0 :(得分:142)

第二种方法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的相同逻辑实现,   只是传递一些默认参数。当您将动作传递给   Task.Run:

Task.Run(someAction);
  

这完全等同于:

Task.Factory.StartNew(someAction, 
    CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

答案 1 :(得分:25)

请参阅描述差异的this blog article。基本上做:

Task.Run(A)

与做:

相同
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);   

答案 2 :(得分:20)

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);
}

答案 3 :(得分:17)

根据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. 不了解异步委托。这实际上是一样的   第1点你想要使用StartNew的原因。问题   当你将异步委托传递给StartNew时,它是很自然的   假设返回的任务代表该委托。但是,自从   StartNew不了解异步委托,实际上是什么任务   代表只是该代表的开始。这是其中之一   编程人员在异步中使用StartNew时遇到的第一个陷阱   码。
  2.   
  3. 混淆默认调度程序。好的,欺骗提问时间:在   下面的代码,方法“A”运行的是什么线程?
  4.   
Task.Factory.StartNew(A);

private static void A() { }
  

嗯,你知道这是一个棘手的问题,嗯?如果你回答“一个线程   池线程“,对不起,但那不对。 “A”将继续运行   无论TaskScheduler当前正在执行什么!

因此,这意味着如果操作完成,它可能会在UI线程上运行,并且由于Stephen Cleary在他的帖子中更全面地解释,它会继续编组回UI线程。

就我而言,我在为视图加载数据网格时尝试在后台运行任务,同时还显示繁忙的动画。使用Task.Factory.StartNew()时未显示繁忙的动画,但切换到Task.Run()时动画显示正确。

有关详细信息,请参阅https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html

答案 4 :(得分:2)

除了相似之处(即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

但是,异步方法会有所不同。

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

在这种情况下,async1的类型为Task ,但是async2的类型为Task >

答案 5 :(得分:1)

人们已经提到了

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的两个参数是不同的:

  1. 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

    < / li>
  2. TaskScheduler-Task.Run使用TaskScheduler.Default,这意味着将始终使用默认任务计划程序(在线程池上运行任务的计划程序)来运行任务。另一方面,Task.Factory.StartNew使用TaskScheduler.Current,这意味着当前线程的调度程序,它可能是TaskScheduler.Default,但并非总是如此。实际上,在开发WinformsWPF应用程序时,如果您无意中在任务中创建了另一个长期运行的任务,则需要从当前线程更新UI,为此人们使用TaskScheduler.FromCurrentSynchronizationContext()任务计划程序使用TaskScheduler.FromCurrentSynchronizationContext()调度程序的用户界面将被冻结。 here

    对此有更详细的说明

    因此,通常,如果您不使用嵌套的子级任务,并且总是希望您的任务在线程池上执行,那么除非您有一些更复杂的方案,否则最好使用Task.Run

答案 6 :(得分:-10)

在我调用两个服务的应用程序中,我比较了Task.Run和 Task.Factory.StartNew 。我发现在我的情况下,它们都工作正常。但是,第二个更快。