startnew run parallel net library example await async asp c# multithreading task-parallel-library

c# - run - task.factory.startnew async



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

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 usando 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 de .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 un hilo de tareas de larga ejecución. Si se va a usar un subproceso 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 subproceso recién creado que se dedicaría a esta tarea y se destruiría una vez que se hubiera completado su tarea. No puede lograr esto con Task.Run , mientras que puede hacerlo con Task.Factory.StartNew , como a continuación:

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

Como se afirma here :

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 montón 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);


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.


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.Run y Task.Factory.StartNew :

  1. TaskCreationOptions - Task.Run usa TaskCreationOptions.DenyChildAttach que significa que las tareas TaskCreationOptions.DenyChildAttach no se pueden adjuntar al padre, 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() , childTask no será esperado, a pesar de que especificamos TaskCreationOptions.AttachedToParent para ello, esto se debe a que TaskCreationOptions.DenyChildAttach prohíbe a los niños adjuntarlo. Si ejecuta el mismo código con Task.Factory.StartNew lugar de Task.Run , parentTask.Wait() esperará childTask porque Task.Factory.StartNew usa TaskCreationOptions.None

  2. TaskScheduler : Task.Run usa TaskScheduler.Default que significa que el programador de tareas predeterminado (el que ejecuta tareas en Thread Pool) siempre se usará para ejecutar tareas. Task.Factory.StartNew por otro lado, utiliza TaskScheduler.Current que significa planificador del subproceso actual, podría ser TaskScheduler.Default pero no siempre. De hecho, al desarrollar aplicaciones Winforms o WPF , se requiere actualizar la interfaz de usuario desde el hilo actual, para que esta gente use TaskScheduler.FromCurrentSynchronizationContext() planificador de tareas, si involuntariamente crea otra tarea de ejecución larga dentro de la tarea que usó TaskScheduler.FromCurrentSynchronizationContext() Scheduler La IU se congelará. Una explicación más detallada de esto se puede encontrar here

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


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 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. :) 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, truco de preguntas: 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!

Entonces, eso significa que podría ejecutarse en el hilo de la interfaz de usuario si se completa una operación y vuelve al hilo de la interfaz de usuario debido a una continuación, como Stephen Cleary explica más detalladamente 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 Task.Factory.StartNew() al usar Task.Factory.StartNew() pero la animación se mostró correctamente cuando Task.Run() a Task.Run() .

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


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


Task.Run se introdujo en la versión más reciente de .NET Framework y se recommended .

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.

Task.Factory.StartNew tiene más opciones, Task.Run 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 shortcut técnico:

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