¿Cuál es la diferencia entre Task.Run () y Task.Factory.StartNew ()

192

Tengo método:

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

Y quiero comenzar este método en una nueva Tarea. Puedo comenzar una nueva tarea como esta

var task = Task.Factory.StartNew(new Action(Method));

o esto

var task = Task.Run(new Action(Method));

Pero, ¿hay alguna diferencia entre Task.Run()y Task.Factory.StartNew(). Ambos están utilizando ThreadPool e inician Method () inmediatamente después de crear la instancia de la Tarea. ¿Cuándo deberíamos usar la primera variante y cuándo la segunda?

Sergiy Lichenko
fuente
66
En realidad, StartNew no tiene que usar ThreadPool, vea el blog al que me vinculé en mi respuesta. El problema son StartNewlos usos predeterminados, TaskScheduler.Currentque pueden ser el grupo de subprocesos, pero también podrían ser el subproceso de la interfaz de usuario.
Scott Chamberlain

Respuestas:

197

El segundo método, Task.Runse ha introducido en una versión posterior del marco .NET (en .NET 4.5).

Sin embargo, el primer método Task.Factory.StartNewle brinda la oportunidad de definir muchas cosas útiles sobre el hilo que desea crear, mientras Task.Runque no proporciona esto.

Por ejemplo, supongamos que desea crear un hilo de tareas de larga ejecución. Si se va a utilizar un subproceso del conjunto de subprocesos para esta tarea, esto podría considerarse un abuso del conjunto de subprocesos.

Una cosa que podría hacer para evitar esto sería ejecutar la tarea en un hilo separado. Un subproceso recién creado que se dedicaría a esta tarea y se destruiría una vez que su tarea se hubiera completado. No puede lograr esto con el Task.Run, mientras que puede hacerlo con el Task.Factory.StartNew, como a continuación:

Task.Factory.StartNew(..., TaskCreationOptions.LongRunning);

Como se afirma aquí :

Entonces, en .NET Framework 4.5 Developer Preview, presentamos el nuevo método Task.Run. Esto de ninguna manera obsoleta Task.Factory.StartNew, sino que simplemente debe considerarse como una forma rápida de usar Task.Factory.StartNew sin necesidad de especificar un grupo de parámetros. Es un atajo. De hecho, Task.Run se implementa en términos de la misma lógica utilizada para Task.Factory.StartNew, simplemente pasando algunos parámetros predeterminados. Cuando pasa una Acción a Task.Run:

Task.Run(someAction);

eso es exactamente equivalente a:

Task.Factory.StartNew(someAction, 
    CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
Christos
fuente
44
Tengo un código donde el estado that’s exactly equivalent tono se mantiene.
Emaborsa
77
@Emaborsa Le agradecería si pudiera publicar este código y elaborar su argumento. Gracias por adelantado !
Christos
44
@Emaborsa Puede crear una esencia, gist.github.com , y compartirla. Sin embargo, excepto al compartir esta esencia, especifique cómo llegó al resultado que la frase tha's exactly equivalent tono contiene. Gracias por adelantado. Sería bueno explicarlo con comentarios sobre su código. Gracias :)
Christos
8
También vale la pena mencionar que Task.Run desenvuelve la tarea anidada de forma predeterminada. Recomiendo leer este artículo sobre las principales diferencias: blogs.msdn.microsoft.com/pfxteam/2011/10/24/…
Pawel Maga
1
@ The0bserver no, lo es TaskScheduler.Default. Por favor, eche un vistazo aquí referencesource.microsoft.com/#mscorlib/system/threading/Tasks/… .
Christos
46

La gente ya mencionó que

Task.Run(A);

Es equivalente a

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

Pero nadie mencionó eso

Task.Factory.StartNew(A);

Es equivalente a:

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

Como puede ver, dos parámetros son diferentes para Task.Runy Task.Factory.StartNew:

  1. TaskCreationOptions- Task.Runusos TaskCreationOptions.DenyChildAttachque significa que las tareas secundarias no se pueden adjuntar a los padres, considere esto:

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

    Cuando invocamos parentTask.Wait(), childTaskno se esperará, a pesar de que lo especificamos TaskCreationOptions.AttachedToParent, esto se debe a que TaskCreationOptions.DenyChildAttachprohíbe que los niños se unan a él. Si ejecuta el mismo código con en Task.Factory.StartNewlugar de Task.Run, parentTask.Wait()esperará childTaskporque Task.Factory.StartNewusaTaskCreationOptions.None

  2. TaskScheduler- Task.Runutiliza, lo TaskScheduler.Defaultque significa que el programador de tareas predeterminado (el que ejecuta tareas en Thread Pool) siempre se utilizará para ejecutar tareas. Task.Factory.StartNewpor otro lado utiliza lo TaskScheduler.Currentque significa planificador del hilo actual, puede ser TaskScheduler.Defaultpero no siempre. De hecho, cuando se desarrollan aplicaciones Winformso WPFse requiere actualizar la interfaz de usuario desde el hilo actual, para que esta gente use el TaskScheduler.FromCurrentSynchronizationContext()programador de tareas, si crea involuntariamente otra tarea de ejecución larga dentro de la tarea que usó el TaskScheduler.FromCurrentSynchronizationContext()programador, la interfaz de usuario se congelará. Una explicación más detallada de esto se puede encontrar aquí

Por lo tanto, en general, si no está utilizando la tarea secundaria anidada y siempre desea que sus tareas se ejecuten en Thread Pool, es mejor usarla Task.Run, a menos que tenga algunos escenarios más complejos.

Mykhailo Seniutovych
fuente
1
Este es un consejo fantástico, debería ser la respuesta aceptada
Ali Bayat
30

Vea este artículo de blog que describe la diferencia. Básicamente haciendo:

Task.Run(A)

Es lo mismo que hacer:

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);   
Scott Chamberlain
fuente
28

Se Task.Runintrodujo en la versión más reciente de .NET Framework y se recomienda .

Comenzando con .NET Framework 4.5, el método Task.Run es la forma recomendada de iniciar una tarea vinculada a proceso. Use el método StartNew solo cuando requiera un control detallado para una tarea de cómputo de larga duración.

El Task.Factory.StartNewtiene más opciones, la Task.Runque es una abreviatura:

El método Run proporciona un conjunto de sobrecargas que facilitan el inicio de una tarea mediante el uso de valores predeterminados. Es una alternativa ligera a las sobrecargas StartNew.

Y por taquigrafía me refiero a un atajo técnico :

public static Task Run(Action action)
{
    return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default,
        TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
}
Zein Makki
fuente
21

Según esta publicación de Stephen Cleary, Task.Factory.StartNew () es peligroso:

Veo mucho código en blogs y en preguntas SO que usan Task.Factory.StartNew para acelerar el trabajo en un hilo de fondo. Stephen Toub tiene un excelente artículo de blog que explica por qué Task.Run es mejor que Task.Factory.StartNew, pero creo que muchas personas simplemente no lo han leído (o no lo entienden). Entonces, tomé los mismos argumentos, agregué un lenguaje más contundente y veremos cómo funciona. :) StartNew ofrece muchas más opciones que Task.Run, pero es bastante peligroso, como veremos. Debería preferir Task.Run sobre Task.Factory.StartNew en código asíncrono.

Aquí están las razones reales:

  1. No entiende a los delegados asíncronos. En realidad, esto es lo mismo que el punto 1 en las razones por las que desea utilizar StartNew. El problema es que cuando pasa un delegado asíncrono a StartNew, es natural suponer que la tarea devuelta representa a ese delegado. Sin embargo, dado que StartNew no entiende a los delegados asíncronos, lo que realmente representa esa tarea es solo el comienzo de ese delegado. Este es uno de los primeros escollos que los codificadores encuentran al usar StartNew en código asíncrono.
  2. Planificador predeterminado confuso. OK, pregunta con truco: en el siguiente código, ¿en qué hilo se ejecuta el método "A"?
Task.Factory.StartNew(A);

private static void A() { }

Bueno, sabes que es una pregunta capciosa, ¿eh? Si respondió "un hilo de grupo de subprocesos", lo siento, pero eso no es correcto. ¡"A" se ejecutará en cualquier TaskScheduler que se esté ejecutando actualmente!

Eso significa que podría ejecutarse en el hilo de la interfaz de usuario si se completa una operación y se vuelve a ordenar al hilo de la interfaz de usuario debido a una continuación como Stephen Cleary explica más completamente en su publicación.

En mi caso, estaba tratando de ejecutar tareas en segundo plano al cargar una cuadrícula de datos para una vista mientras también mostraba una animación ocupada. La animación ocupada no se mostraba cuando se usaba, Task.Factory.StartNew()pero la animación se mostraba correctamente cuando cambiaba a Task.Run().

Para más detalles, consulte https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html

usuario8128167
fuente
1

Además de las similitudes, es decir, Task.Run () es una abreviatura de Task.Factory.StartNew (), hay una pequeña diferencia entre su comportamiento en caso de delegados sincronizados y asíncronos.

Supongamos que hay dos métodos siguientes:

public async Task<int> GetIntAsync()
{
    return Task.FromResult(1);
}

public int GetInt()
{
    return 1;
}

Ahora considere el siguiente código.

var sync1 = Task.Run(() => GetInt());
var sync2 = Task.Factory.StartNew(() => GetInt());

Aquí, sync1 y sync2 son del tipo Task <int>

Sin embargo, la diferencia viene en caso de métodos asíncronos.

var async1 = Task.Run(() => GetIntAsync());
var async2 = Task.Factory.StartNew(() => GetIntAsync());

En este escenario, async1 es del tipo Task <int>, sin embargo, async2 es del tipo Task <Task <int>>

Shubham Sharma
fuente
Yeap, porque Task.Runtiene incorporada la funcionalidad de desenvolvimiento del Unwrapmétodo. Aquí hay una publicación de blog que explica el razonamiento detrás de esta decisión.
Theodor Zoulias
-8

En mi aplicación que llama a dos servicios, comparé Task.Run y Task.Factory.StartNew . Descubrí que en mi caso ambos funcionan bien. Sin embargo, el segundo es más rápido.

Devendra Rusia
fuente
No sé por qué esta respuesta merece "10" votos negativos, a pesar de que podría no ser correcta o útil ...
Mayer Spitzer