我有方法:
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()。当我们应该使用第一个变体和第二个时?
答案 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点你想要使用StartNew的原因。问题 当你将异步委托传递给StartNew时,它是很自然的 假设返回的任务代表该委托。但是,自从 StartNew不了解异步委托,实际上是什么任务 代表只是该代表的开始。这是其中之一 编程人员在异步中使用StartNew时遇到的第一个陷阱 码。
- 混淆默认调度程序。好的,欺骗提问时间:在 下面的代码,方法“A”运行的是什么线程?
醇>
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
答案 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.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()
调度程序的用户界面将被冻结。 here
因此,通常,如果您不使用嵌套的子级任务,并且总是希望您的任务在线程池上执行,那么除非您有一些更复杂的方案,否则最好使用Task.Run
。
答案 6 :(得分:-10)
在我调用两个服务的应用程序中,我比较了Task.Run和 Task.Factory.StartNew 。我发现在我的情况下,它们都工作正常。但是,第二个更快。