ChatGPT解决这个技术问题 Extra ChatGPT

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

我有方法:

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 线程。

S
Stein

第二种方法 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 时:

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/…
M
Mykhailo Seniutovych

人们已经提到过

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("子任务完成。"); }, TaskCreationOptions.AttachedToParent); childTask.Start(); Console.WriteLine("父任务完成"); });父任务.Wait(); Console.WriteLine("主线程完成。");

当我们调用 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 将被冻结。可以在这里找到更详细的解释

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


这是一个很棒的提示,应该是公认的答案
很好,但即使你有嵌套任务,你也可以使用 Task.Run().Unwrap().ContinueWith() ,对吧?
B
Bugs

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

Task.Run(A)

和做的一样:

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

R
Rekshino

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

u
user8128167

根据 Stephen Cleary 的这篇文章,Task.Factory.StartNew() 很危险:

我在博客和 SO 问题中看到很多代码,它们使用 Task.Factory.StartNew 在后台线程上启动工作。 Stephen Toub 有一篇优秀的博客文章解释了为什么 Task.Run 比 Task.Factory.StartNew 更好,但我认为很多人只是没有读过它(或者不理解它)。所以,我采用了相同的论点,添加了一些更有力的语言,我们将看看这是怎么回事。 :) StartNew 确实提供了比 Task.Run 更多的选项,但正如我们将看到的那样,它非常危险。在异步代码中,您应该更喜欢 Task.Run 而不是 Task.Factory.StartNew。

以下是实际原因:

不理解异步委托。这实际上与您想要使用 StartNew 的原因中的第 1 点相同。问题在于,当您将异步委托传递给 StartNew 时,很自然地会假设返回的任务代表该委托。但是,由于 StartNew 不理解异步委托,因此该任务实际代表的只是该委托的开始。这是编码人员在异步代码中使用 StartNew 时遇到的第一个陷阱。令人困惑的默认调度程序。好的,技巧问题时间:在下面的代码中,方法“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


S
Shubham Sharma

除了相似之处,即 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 是一篇博文,解释了这一决定背后的原因。