Traducido de la publicación del blog de Stephen Toub "Task.Run vs Task.Factory.StartNew" el 24 de octubre de 2011. Stephen Toub es el arquitecto principal del equipo de Microsoft Parallel Computing Platform.
En .NET 4 , Task.Factory.StartNew
es el método preferido para programar nuevas tareas. Tiene muchas sobrecargas y proporciona un mecanismo altamente configurable. Al habilitar las opciones de configuración, puede pasar estados arbitrarios, habilitar la cancelación e incluso controlar el comportamiento de programación. El otro lado de todas estas funciones es la complejidad. Necesita saber cuándo usar qué sobrecarga, qué programador proporcionar, etc. Además, Task.Factory.StartNew
no es fácil de usar, al menos no lo suficientemente rápido para algunos de sus escenarios de uso, como su escenario de uso principal: entrega fácilmente el trabajo a los subprocesos de procesamiento en segundo plano.
Por lo tanto, en .NET Framework 4.5 Developer Preview , presentamos un nuevo Task.Run
método. Esto no debe eliminarse Task.Factory.StartNew
, simplemente debe pensar que es para usar Task.Factory.StartNew
y no tiene una forma conveniente de pasar un montón de parámetros. Este es un atajo. De hecho, Task.Run
en realidad , de acuerdo con la Task.Factory.StartNew
realización de la misma lógica, acaba de pasar algunos parámetros predeterminados. Cuando pasa un Action
dado Task.Run
:
Task.Run(someAction);
Es exactamente equivalente a:
Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
De esta manera, Task.Run
puede y debe usarse en los escenarios más comunes, simplemente entregue el trabajo al grupo de subprocesos para su ThreadPool
procesamiento (es decir, el destino del parámetro TaskScheduler.Default). Esto no quiere decir que Task.Factory.StartNew
ya no se vaya a utilizar, ni mucho menos, Task.Factory.StartNew
hay muchas aplicaciones importantes (aunque más avanzadas). Puede controlar el TaskCreationOptions
comportamiento de las tareas de control, puede controlar TaskScheduler
las tareas de programación y para controlar el funcionamiento del objeto receptor se puede usar en estado sobrecargado, para la ruta del código sensible al rendimiento, para evitar sobrecargar el uso de cierres y la asignación de memoria correspondiente. Sin embargo, para casos sencillos, Task.Run
es tu amigo.
Task.Run
Se proporcionan ocho sobrecargas para admitir todas las siguientes combinaciones:
- Tareas sin valor de retorno (
Task
) y tareas con valor de retorno (Task<TResult>
) - Admite cancelación (
cancelable
) y no admite cancelación (non-cancelable
) - Delegado sincrónico (
synchronous delegate
) y delegado asincrónico (asynchronous delegate
)
Los dos primeros puntos deben ser evidentes. Para el primer punto, devuelve Task
sobrecarga (sin valor de retorno para la operación), así como Task<TResult>
sobrecarga de retorno (para el tipo TResult
de operación de retorno ). Para el segundo punto, y aceptando la CancellationToken
sobrecarga, si la solicitud se cancela antes de que la tarea haya comenzado, la tarea Biblioteca paralela de tareas (el TPL) se puede convertir a un estado cancelado.
El tercer punto es más interesante, está directamente relacionado con el soporte de lenguaje asincrónico de C # y Visual Basic en Visual Studio 11 . Pensemos en ello por un momento Task.Factory.StartNew
, lo que ayudará a resaltar esta distinción. Si escribo la siguiente llamada:
var t = Task.Factory.StartNew(() =>
{
Task inner =Task.Factory.StartNew(() => {
});
return inner;
});
Donde será "t" del tipo Task<Task>
, para el tipo de tarea que se confía Func<TResult>
en esta realización TResult
es, Task
por StartNew
lo tanto, el valor de retorno es Task<Task>
. Del mismo modo, si cambio el código a:
var t = Task.Factory.StartNew(() =>
{
Task<int> inner = Task.Factory.StartNew(() => 42));
return inner;
});
En este momento, el tipo de "t" aquí será Task<Task<int>>
. Debido a que se confía el tipo de tarea Func<TResult>
, cuando TResult
Shi Task<int>
, StartNew
el valor de retorno es Task<Task<int>>
. ¿Por qué es esto relevante? Ahora considere lo que sucede si escribo el siguiente código:
var t = Task.Factory.StartNew(async delegate
{
await Task.Delay(1000);
return 42;
});
Al usar esta async
palabra clave, el compilador será el delegado ( delegate
) asignado Func<Task<int>>
, la llamada que el delegado devolverá Task<int>
representa la finalización final de esta llamada. Porque el delegado es Func<Task<int>>
, TResult
sí Task<int>
, entonces aquí el tipo de "t" será Task<Task<int>>
, no Task<int>
.
Para manejar tales situaciones, el .NET 4 presentamos un Unwrap
método. Unwrap
Hay dos sobrecargas de métodos, ambos son métodos de extensión, uno para tipos Task<Task>
y otro para tipos Task<Task<TResult>>
. Llamamos a este método Unwrap
porque en realidad "descomprime" la tarea interna y devuelve el valor de retorno de la tarea interna como el valor de retorno de la tarea externa. Para Task<Task>
llamar Unwrap
devuelve una nueva Task
(que normalmente se conoce como agente), que representa la completa finalización de la tarea interna. Del mismo modo, finalizó un Task<Task<TResult>>
llamado a Unwrap
volver a una nueva Task<TResult>
representación de la tarea interna. (En ambos casos, si la tarea externa falla o se cancela, no hay tarea interna, porque la tarea que no se ha ejecutado hasta su finalización no producirá resultados, por lo que la tarea proxy representa el estado de la tarea externa). el ejemplo anterior, si quiero que "t" represente el valor de retorno de esa tarea interna (en este ejemplo, el valor es 42), puedo escribir:
var t = Task.Factory.StartNew(async delegate
{
await Task.Delay(1000);
return 42;
}).Unwrap();
Ahora, aquí, el tipo de la variable "t" será Task<int>
, que representa el valor de retorno de la llamada asincrónica.
Volver Task.Run
. Debido a que queremos que la gente transfiera el trabajo al grupo de subprocesos ( ThreadPool
) y el uso se async/await
convierta en algo común, decidimos desempaquetar ( unwrapping
) la función integrada en el Task.Run
medio. Este es el contenido mencionado en el tercer punto anterior. Hay una variedad Task.Run
de tareas pesadas, aceptadas Action
(sin valor de retorno para la tarea), Func<TResult>
(para volver a TResult
la tarea), Func<Task>
(sin valor de retorno para tareas asincrónicas) y Func<Task<TResult>>
(para el retorno TResult
de tarea asincrónica). Internamente, Task.Run
realiza el Task.Factory.StartNew
mismo tipo anterior que se muestra en la unwrapping
operación desempaquetada ( ). Entonces cuando escribo:
var t = Task.Run(async delegate
{
await Task.Delay(1000);
return 42;
});
El tipo de "t" es Task<int>
que Task.Run
esta implementación sobrecargada es básicamente equivalente a:
var t = Task.Factory.StartNew(async delegate
{
await Task.Delay(1000);
return 42;
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();
Como se mencionó anteriormente, este es un atajo.
Todo esto significa que puede Task.Run
usarlo junto con los métodos lambdas / anónimos convencionales o el método lambdas / anónimo asincrónico, sucederá lo correcto. Si quiero entregar el trabajo al grupo de subprocesos ( ThreadPool
) y esperar su resultado, por ejemplo:
int result = await Task.Run(async () =>
{
await Task.Delay(1000);
return 42;
});
El result
tipo de variable será int
, como era de esperar, esta tarea después de llamar aproximadamente un segundo, el result
valor de la variable se establecerá en 42.
Curiosamente, puede encontrar await
palabras clave casi nuevas y consideradas como Unwrap
un método alternativo de lenguaje. Así, si volvemos al Task.Factory.StartNew
ejemplo, se puede utilizar Unwrap
para reescribir el último fragmento de código anterior, de la siguiente manera:
int result = await Task.Factory.StartNew(async delegate
{
await Task.Delay(1000);
return 42;
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();
O puedo usar el segundo await
lugar de uso Unwrap
:
int result = await await Task.Factory.StartNew(async delegate
{
await Task.Delay(1000);
return 42;
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
El "esperar esperar" aquí no es un error de entrada, Task.Factory.StartNew
volver Task<Task<int>>
. await
Task<Task<int>>
Vuelve Task<int>
, luego await
Task<int>
vuelve int
, muy interesante, ¿no?
Autor: Stephen Toub
Traductor: Technical Zemin
Editor: Technical Verses
enlaces: el texto original en inglés
© traducción puramente manual, bienvenido a reimprimir, por favor indique la fuente