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

Tengo un 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 usan 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?

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

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

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

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

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

Como se afirma aquí :

Por lo tanto, en .NET Framework 4.5 Developer Preview, presentamos el nuevo método Task.Run. Esto de ninguna manera obsoleta Task.Factory.StartNew, sino que debe ser simplemente una manera 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 realidad 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 Tarea. Ejecutar:

 Task.Run(someAction); 

eso es exactamente equivalente a:

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

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

El segundo se introdujo en la versión más reciente de .NET framework y se recomienda . El primero tiene más opciones, el segundo es una abreviatura:

El método Run proporciona un conjunto de sobrecargas que facilitan el inicio de una tarea utilizando valores predeterminados. Es una alternativa ligera a las sobrecargas de 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); } 

Según este post de Stephen Cleary, Task.Factory.StartNew () es peligroso:

Veo un montón de código en los blogs y en las preguntas de SO que usan Task.Factory.StartNew para boost 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 mucha gente simplemente no lo ha leído (o no lo entiende). Entonces, tomé los mismos argumentos, agregué un lenguaje más contundente, y veremos cómo funciona esto. 🙂 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 async.

Aquí están las razones reales:

  1. No entiende a los delegates asincrónicos. Esto es realmente lo mismo que el punto 1 en las razones por las que desea usar StartNew. El problema es que cuando pasas 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 delegates asíncronos, lo que esa tarea realmente representa es solo el comienzo de ese delegado. Este es uno de los primeros escollos que encuentran los codificadores al usar StartNew en el código asíncrono.
  2. Progtwigdor predeterminado confuso. OK, truco tiempo de pregunta: en el código de abajo, ¿qué hilo 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 del grupo de subprocesos”, lo siento, pero eso no es correcto. “A” se ejecutará en lo que TaskScheduler esté ejecutando actualmente.

Entonces eso significa que potencialmente podría ejecutarse en el hilo de la interfaz de usuario si una operación finaliza y regresa al hilo de la interfaz de usuario debido a una continuación, como explica más ampliamente Stephen Cleary en su publicación.

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

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

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.