.NET - Task.Run vs Task.Factory.StartNew

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.StartNewes 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.StartNewno 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.Runmétodo. Esto no debe eliminarse Task.Factory.StartNew, simplemente debe pensar que es para usar Task.Factory.StartNewy no tiene una forma conveniente de pasar un montón de parámetros. Este es un atajo. De hecho, Task.Runen realidad , de acuerdo con la Task.Factory.StartNewrealización de la misma lógica, acaba de pasar algunos parámetros predeterminados. Cuando pasa un Actiondado Task.Run:

Task.Run(someAction);

Es exactamente equivalente a:

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

De esta manera, Task.Runpuede y debe usarse en los escenarios más comunes, simplemente entregue el trabajo al grupo de subprocesos para su ThreadPoolprocesamiento (es decir, el destino del parámetro TaskScheduler.Default). Esto no quiere decir que Task.Factory.StartNewya no se vaya a utilizar, ni mucho menos, Task.Factory.StartNewhay muchas aplicaciones importantes (aunque más avanzadas). Puede controlar el TaskCreationOptionscomportamiento de las tareas de control, puede controlar TaskSchedulerlas 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.Runes tu amigo.

Task.Run Se proporcionan ocho sobrecargas para admitir todas las siguientes combinaciones:

  1. Tareas sin valor de retorno ( Task) y tareas con valor de retorno ( Task<TResult>)
  2. Admite cancelación ( cancelable) y no admite cancelación ( non-cancelable)
  3. Delegado sincrónico ( synchronous delegate) y delegado asincrónico ( asynchronous delegate)

Los dos primeros puntos deben ser evidentes. Para el primer punto, devuelve Tasksobrecarga (sin valor de retorno para la operación), así como Task<TResult>sobrecarga de retorno (para el tipo TResultde operación de retorno ). Para el segundo punto, y aceptando la CancellationTokensobrecarga, 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 TResultes, Taskpor StartNewlo 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 TResultShi Task<int>, StartNewel 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 asyncpalabra 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>>, TResultTask<int>, entonces aquí el tipo de "t" será Task<Task<int>>, no Task<int>.

Para manejar tales situaciones, el .NET 4 presentamos un Unwrapmétodo. UnwrapHay 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 Unwrapporque 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 Unwrapdevuelve 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 Unwrapvolver 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/awaitconvierta en algo común, decidimos desempaquetar ( unwrapping) la función integrada en el Task.Runmedio. Este es el contenido mencionado en el tercer punto anterior. Hay una variedad Task.Runde tareas pesadas, aceptadas Action(sin valor de retorno para la tarea), Func<TResult>(para volver a TResultla tarea), Func<Task>(sin valor de retorno para tareas asincrónicas) y Func<Task<TResult>>(para el retorno TResultde tarea asincrónica). Internamente, Task.Runrealiza el Task.Factory.StartNewmismo tipo anterior que se muestra en la unwrappingoperació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.Runesta 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.Runusarlo 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 resulttipo de variable será int, como era de esperar, esta tarea después de llamar aproximadamente un segundo, el resultvalor de la variable se establecerá en 42.

Curiosamente, puede encontrar awaitpalabras clave casi nuevas y consideradas como Unwrapun método alternativo de lenguaje. Así, si volvemos al Task.Factory.StartNewejemplo, se puede utilizar Unwrappara 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 awaitlugar 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.StartNewvolver 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
Número público: Estación de traducción técnica

Supongo que te gusta

Origin blog.csdn.net/weixin_47498376/article/details/108228157
Recomendado
Clasificación