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

Ser*_*nko 155 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");
}
Run Code Online (Sandbox Code Playgroud)

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

var task = Task.Factory.StartNew(new Action(Method));
Run Code Online (Sandbox Code Playgroud)

或这个

var task = Task.Run(new Action(Method));
Run Code Online (Sandbox Code Playgroud)

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

Chr*_*tos 166

第二种方法Task.Run已经在.NET框架的更高版本中引入(在.NET 4.5中).

但是,第一种方法Task.Factory.StartNew使您有机会定义有关要创建的线程的许多有用信息,Task.Run而不提供此信息.

例如,假设您要创建一个长时间运行的任务线程.如果线程池的线程将用于此任务,则可以将此视为滥用线程池.

为避免这种情况,您可以做的一件事是在单独的线程中运行任务.一个新创建的线程,专门用于此任务,一旦您的任务完成就会被销毁.无法实现这一点Task.Run,而你可以这样做Task.Factory.StartNew,如下所示:

Task.Factory.StartNew(..., TaskCreationOptions.LongRunning);
Run Code Online (Sandbox Code Playgroud)

正如这里所说:

因此,在.NET Framework 4.5 Developer Preview中,我们引入了新的Task.Run方法. 这绝不会废弃 Task.Factory.StartNew, 而应简单地将其视为使用 Task.Factory.StartNew 的快速方法,而无需指定一堆参数.这是捷径. 实际上,Task.Run实际上是根据用于Task.Factory.StartNew的相同逻辑实现的,只是传入一些默认参数.将Action传递给Task.Run时:

Task.Run(someAction);
Run Code Online (Sandbox Code Playgroud)

这完全等同于:

Task.Factory.StartNew(someAction, 
    CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
Run Code Online (Sandbox Code Playgroud)

  • @Emaborsa我很感激如果您可以发布这段代码并详细说明您的论点.提前致谢 ! (7认同)
  • 值得一提的是,Task.Run默认解包嵌套任务.我建议阅读这篇关于主要差异的文章:https://blogs.msdn.microsoft.com/pfxteam/2011/10/24/task-run-vs-task-factory-startnew/ (7认同)
  • 我有一段代码,其中"完全等同于`的statemente不成立. (4认同)
  • @Emaborsa你可以创建一个要点,https://gist.github.com/,并分享它.但是,除了分享这个要点之外,请说明你是如何得出"tha完全等同于"这句话不能成立的结果.提前致谢.通过评论您的代码来解释会很好.谢谢 :) (4认同)
  • “这完全相当于”确实具有误导性,[文章](https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/)的后半部分解释了原因。如果您想要证据,请运行 [this gist](https://gist.github.com/ssomers/17c961ff80b17955fd8674d191d2c149) 作为控制台应用程序。我认为正确的措辞是“完全相当于” `Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap()` 或没有 `.Unwrap()` 的相同内容。 (2认同)

Sco*_*ain 25

请参阅此博客文章,其中介绍了差异.基本上做:

Task.Run(A)
Run Code Online (Sandbox Code Playgroud)

和做的一样:

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);   
Run Code Online (Sandbox Code Playgroud)


Zei*_*kki 22

Task.Run是在较新的.NET框架版本中引入的,建议使用.

从.NET Framework 4.5开始,Task.Run方法是启动计算绑定任务的推荐方法.仅当您需要对长时间运行的计算绑定任务进行细粒度控制时,才使用StartNew方法.

Task.Factory.StartNew有更多的选择,Task.Run是一个简写:

Run方法提供了一组重载,可以使用默认值轻松启动任务.它是StartNew重载的轻量级替代品.

简而言之,我的意思是技术捷径:

public static Task Run(Action action)
{
    return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default,
        TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
}
Run Code Online (Sandbox Code Playgroud)


use*_*167 18

根据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的原因中的第1点相同.问题是,当您将异步委托传递给StartNew时,很自然地假设返回的任务代表该委托.但是,由于StartNew不了解异步委托,因此该任务实际代表的只是该委托的开头.这是编码器在异步代码中使用StartNew时遇到的第一个陷阱之一.
  2. 令人困惑的默认调度程序.好的,欺骗提问时间:在下面的代码中,方法"A"运行的是什么线程?
Task.Factory.StartNew(A);

private static void A() { }
Run Code Online (Sandbox Code Playgroud)

嗯,你知道这是一个棘手的问题,嗯?如果您回答"线程池线程",我很抱歉,但这不正确."A"将在TaskScheduler当前正在执行的任何内容上运行!

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

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

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


Myk*_*ych 16

人们已经提到

Task.Run(A);
Run Code Online (Sandbox Code Playgroud)

相当于

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
Run Code Online (Sandbox Code Playgroud)

但是没有人提到

Task.Factory.StartNew(A);
Run Code Online (Sandbox Code Playgroud)

等效于:

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current);
Run Code Online (Sandbox Code Playgroud)

如您所见,Task.Run和的两个参数不同Task.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.");
    
    Run Code Online (Sandbox Code Playgroud)

    当我们调用时parentTask.Wait()childTask即使我们指定TaskCreationOptions.AttachedToParent了它,也不会等待,这是因为TaskCreationOptions.DenyChildAttach禁止孩子附加到它。如果您使用Task.Factory.StartNew代替来运行相同的代码Task.RunparentTask.Wait()则会等待,childTask因为Task.Factory.StartNew使用TaskCreationOptions.None

  2. TaskScheduler- Task.Run使用TaskScheduler.Default表示默认任务计划程序(在线程池上运行任务的计划程序)将始终用于运行任务。Task.Factory.StartNew另一方面,使用TaskScheduler.Current表示当前线程的调度程序,它可能是TaskScheduler.Default但并非总是如此。实际上,在开发WinformsWPF应用程序时,需要从当前线程更新UI,为此,人们可以使用TaskScheduler.FromCurrentSynchronizationContext()任务计划程序,如果您在使用该TaskScheduler.FromCurrentSynchronizationContext()计划程序的任务内无意间创建了另一个长期运行的任务,则该UI将被冻结。可以在这里找到更详细的解释

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

  • 这是一个很棒的提示,应该是公认的答案 (2认同)

Shu*_*rma 5

除了相似之处,即 Task.Run() 是 Task.Factory.StartNew() 的简写外,在同步和异步委托的情况下,它们的行为之间存在细微差别。

假设有以下两种方法:

public async Task<int> GetIntAsync()
{
    return Task.FromResult(1);
}

public int GetInt()
{
    return 1;
}
Run Code Online (Sandbox Code Playgroud)

现在考虑以下代码。

var sync1 = Task.Run(() => GetInt());
var sync2 = Task.Factory.StartNew(() => GetInt());
Run Code Online (Sandbox Code Playgroud)

这里的 sync1 和 sync2 都是类型 Task<int>

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

var async1 = Task.Run(() => GetIntAsync());
var async2 = Task.Factory.StartNew(() => GetIntAsync());
Run Code Online (Sandbox Code Playgroud)

在这种情况下, async1 的类型为Task<int>,而 async2 的类型为Task<Task<int>>