我有方法:
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
默认使用 TaskScheduler.Current
,它可能是线程池,但也可能是 UI 线程。
第二种方法 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);
人们已经提到过
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("子任务完成。"); }, 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
,除非您有一些更复杂的场景。
请参阅描述差异的 this blog article。基本上在做:
Task.Run(A)
和做的一样:
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
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);
}
根据 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
除了相似之处,即 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>>
类型
that’s exactly equivalent to
不成立。tha's exactly equivalent to
不成立的结果。提前致谢。最好用对您的代码的评论来解释。谢谢 :)TaskScheduler.Default
。请在此处查看referencesource.microsoft.com/#mscorlib/system/threading/Tasks/…。