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

11 浏览
0 Comments

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()。应该在什么时候使用第一种变体,什么时候使用第二种变体?

admin 更改状态以发布 2023年5月21日
0
0 Comments

人们已经提到了

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的两个参数是不同的:

  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.");
    

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

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

所以通常,如果您不使用嵌套子任务并始终希望在线程池上执行您的任务,则最好使用Task.Run,除非您有更复杂的情况。

0
0 Comments

第二种方法是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);

0