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
:
-
TaskCreationOptions
-Task.Run
usaTaskCreationOptions.DenyChildAttach
que significa que las tareasTaskCreationOptions.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 especificamosTaskCreationOptions.AttachedToParent
para ello, esto se debe a queTaskCreationOptions.DenyChildAttach
prohíbe a los niños adjuntarlo. Si ejecuta el mismo código conTask.Factory.StartNew
lugar deTask.Run
,parentTask.Wait()
esperaráchildTask
porqueTask.Factory.StartNew
usaTaskCreationOptions.None
-
TaskScheduler
:Task.Run
usaTaskScheduler.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, utilizaTaskScheduler.Current
que significa planificador del subproceso actual, podría serTaskScheduler.Default
pero no siempre. De hecho, al desarrollar aplicacionesWinforms
oWPF
, se requiere actualizar la interfaz de usuario desde el hilo actual, para que esta gente useTaskScheduler.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:
- 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.
- 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);
}