Task.Run() 和 Task.Factory.StartNew() 有什么区别?
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()
之间有什么区别呢?它们两者都使用线程池并在创建任务实例后立即启动Method()。应该在什么时候使用第一种变体,什么时候使用第二种变体?
人们已经提到了
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()调度程序的任务中创建另一个长时间运行的任务,则UI将被冻结。详细的解释可以在这里找到
所以通常,如果您不使用嵌套子任务并始终希望在线程池上执行您的任务,则最好使用Task.Run,除非您有更复杂的情况。
第二种方法是Task.Run
,它在.NET框架的较新版本(即.NET 4.5)中引入。
然而,第一种方法Task.Factory.StartNew
使您有机会定义关于要创建的线程的许多有用信息,而Task.Run
不提供此功能。
例如,假设您想要创建一个长时间运行的任务线程。如果线程池的线程要用于此任务,则可能被视为滥用线程池。
为避免这种情况,您可以在单独的线程中运行任务。新创建的线程将专用于此任务,并在完成任务后将被销毁。您无法使用Task.Run
实现此功能,而您可以使用Task.Factory.StartNew
实现此功能,如下所示:
Task.Factory.StartNew(..., TaskCreationOptions.LongRunning);
正如此处所述:
因此,在.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);
这完全等同于:
Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);