Para ver el artículo en inglés, active la casilla Inglés. También puede ver el texto en inglés en una ventana emergente si pasa el puntero del mouse por el texto.
Traducción
Inglés

Task-based Asynchronous Pattern (TAP)

.NET Framework (current version)
 

El patrón asincrónico basado en tareas (TAP) se basa en los tipos System.Threading.Tasks.Task y System.Threading.Tasks.Task<TResult> del espacio de nombres System.Threading.Tasks, que se usan para representar operaciones asincrónicas arbitrarias. TAP es el patrón asincrónico de diseño recomendado para los nuevos desarrollos.

TAP usa un solo método para representar el inicio y la finalización de una operación asincrónica. Esto contrasta con el modelo de programación asincrónica (APM o IAsyncResult), que necesita los métodos Begin y End, y también con el patrón asincrónico basado en eventos (EAP), que necesita un método que tenga el sufijo Async y también necesita uno o más eventos, tipos de delegado de controlador de eventos y tipos derivados de EventArg. Los métodos asincrónicos de TAP incluyen el sufijo Async después del nombre de la operación; por ejemplo, GetAsync para una operación Get. Si va a agregar un método de TAP a una clase que ya contiene ese nombre de método con el sufijo Async, use el sufijo TaskAsync en su lugar. Por ejemplo, si la clase ya tiene un método GetAsync, use el nombre GetTaskAsync.

El método de TAP devuelve System.Threading.Tasks.Task o System.Threading.Tasks.Task<TResult>, en función de si el método sincrónico correspondiente devuelve void o un tipo TResult.

Los parámetros de un método de TAP deben coincidir con los parámetros de su homólogo sincrónico y se deben proporcionar en el mismo orden. Sin embargo, los parámetros out y ref están exentos de esta regla y se deben evitar completamente. En su lugar, los datos que se hubieran devuelto con un parámetro out o ref se deben devolver como parte del tipo TResult devuelto por Task<TResult> y deben usar una tupla o una estructura de datos personalizada para incluir varios valores. Los métodos que están dedicados exclusivamente a la creación, manipulación o combinación de tareas (donde el intento asincrónico del método está claro en el nombre del método o en el nombre del tipo al que el método pertenece) no necesitan seguir este patrón de nombres; esos métodos se conocen a menudo como combinadores (también denominados elementos de combinación). Los ejemplos de combinadores incluyen WhenAll y WhenAny, y se describen en la sección que describe los combinadores integrados basados en tareas titulada Utilizar los elementos de combinación basados en tareas integradas del artículo Consuming the Task-based Asynchronous Pattern.

Para obtener ejemplos de cómo la sintaxis de TAP difiere de la sintaxis empleada en patrones heredados de programación asincrónica como el modelo de programación asincrónica (APM) y el patrón asincrónico basado en eventos (EAP), vea Asynchronous Programming Patterns.

Un método asincrónico basado en TAP puede hacer una pequeña cantidad de trabajo sincrónicamente, como validar argumentos e iniciar la operación asincrónica, antes de que devuelva la tarea resultante. El trabajo sincrónico debe reducirse al mínimo de modo que el método asincrónico pueda volver rápidamente. Entre las razones para un retorno rápido se incluyen las siguientes:

  • Los métodos asincrónicos se pueden invocar desde subprocesos de la interfaz de usuario (UI) y cualquier trabajo sincrónico de ejecución prolongada puede dañar la capacidad de respuesta de la aplicación.

  • Se pueden iniciar varios métodos asincrónicos simultáneamente. Por tanto, cualquier trabajo de ejecución prolongada en la parte sincrónica de un método asincrónico puede retrasar el inicio de otras operaciones asincrónicas, lo que reduce las ventajas de la simultaneidad.

En algunos casos, la cantidad de trabajo necesario para completar la operación es menor que la cantidad de trabajo necesario para iniciar la operación de forma asincrónica. La lectura de una secuencia donde la operación de lectura se puede satisfacer mediante datos que ya están almacenados en búfer en la memoria es un ejemplo de este escenario. En casos como este, la operación puede completarse sincrónicamente y puede devolver una tarea que ya se ha completado.

Un método asincrónico debe generar una excepción fuera de la llamada de método asincrónico solo como respuesta a un error de uso. Los errores de uso nunca deben producirse en código de producción. Por ejemplo, si al pasar una referencia nula (Nothing en Visual Basic) como uno de los argumentos del método se produce un estado de error (representado normalmente por una excepción ArgumentNullException), puede modificar el código de llamada para asegurarse de que nunca se pase una referencia nula. Para todos los demás errores, las excepciones que se producen cuando se ejecuta un método asincrónico deben asignarse a la tarea devuelta, aunque el método asincrónico se complete sincrónicamente antes de que se devuelva la tarea. Normalmente, una tarea contiene como máximo una excepción. Sin embargo, si la tarea representa varias operaciones (por ejemplo, WhenAll), se pueden asociar varias excepciones a una única tarea.

Cuando implementa un método de TAP, puede determinar dónde se produce la ejecución asincrónica. Puede elegir ejecutar la carga de trabajo en el grupo de subprocesos, implementarla mediante E/S asincrónica (sin enlazarse a un subproceso para la mayor parte de la ejecución de la operación), ejecutarla en un subproceso concreto (como el subproceso de la interfaz de usuario) o usar cualquier número de contextos posibles. Un método de TAP puede no tener nada que ejecutar y puede devolver simplemente Task, que representa la existencia de una condición en otra parte del sistema (por ejemplo, una tarea que representa los datos que llegan a una estructura de datos en cola). El llamador del método de TAP puede bloquear la espera hasta que se complete el método de TAP esperando sincrónicamente la tarea resultante o puede ejecutar código adicional (de continuación) cuando la operación asincrónica se complete. El creador del código de continuación tiene control sobre lo que ese código ejecuta. Puede crear el código de continuación explícitamente, mediante métodos de la clase Task (por ejemplo, ContinueWith) o implícitamente, usando la compatibilidad con lenguaje sobre las continuaciones (por ejemplo, await en C#, Await en Visual Basic, AwaitValue en F#).

La clase Task proporciona un ciclo de vida para las operaciones asincrónicas y ese ciclo se representa mediante la enumeración TaskStatus. Para admitir los casos extremos de tipos que se derivan de Task y Task<TResult>, y para admitir la separación de la construcción de la programación, la clase Task expone un método Start. Las tareas creadas por los constructores públicos Task se denominan tareas en frío, porque inician su ciclo de vida en el estado Created no programado y solo se programan cuando se llama a Start en estas instancias. Todas las demás tareas inician su ciclo de vida en un estado activo, lo que significa que las operaciones asincrónicas que representan ya se han iniciado y su estado de la tarea es un valor de enumeración distinto de TaskStatus.Created. Todas las tareas que se devuelven de métodos de TAP deben estar activas. Si un método de TAP usa internamente el constructor de una tarea para crear instancias de la tarea que se va a devolver, el método de TAP debe llamar a Start en el objeto Task antes de devolverlo. Los consumidores de un método de TAP pueden suponer con seguridad que la tarea devuelta está activa y no deben intentar llamar a Start en ningún Task que se devuelve de un método de TAP. La llamada a Start en una tarea activa produce una excepción InvalidOperationException.

En TAP, la cancelación es opcional tanto para los implementadores de método asincrónico como para los consumidores de este método. Si una operación permite la cancelación, expone una sobrecarga del método asincrónico que acepta un token de cancelación (instancia de CancellationToken). Por convención, el parámetro se denomina cancellationToken.

public Task ReadAsync(byte [] buffer, int offset, int count, 
                      CancellationToken cancellationToken)

La operación asincrónica supervisa este token para las solicitudes de cancelación. Si recibe una solicitud de cancelación, puede elegir admitir esa solicitud y cancelar la operación. Si la solicitud de cancelación hace que el trabajo finalice prematuramente, el método de TAP devuelve una tarea que finaliza en el estado Canceled; no hay ningún resultado disponible y no se produce ninguna excepción. El estado Canceled se considera un estado final (completado) para una tarea, junto con los estados Faulted y RanToCompletion. Por tanto, si una tarea está en el estado Canceled, su propiedad IsCompleted devuelve true. Cuando una tarea se completa en el estado Canceled, cualquier continuación registrada con la tarea se programa o se ejecuta, a menos que se especificara una opción de continuación como NotOnCanceled para rechazar la continuación. Cualquier código que espera de forma asincrónica una tarea cancelada mediante el uso de características del lenguaje sigue ejecutándose pero recibe un objeto OperationCanceledException o una excepción derivada del mismo. El código que se bloquea sincrónicamente en espera de la tarea mediante métodos como Wait y WaitAll también continúa ejecutándose con una excepción.

Si un token de cancelación ha solicitado la cancelación antes de que se llame al método de TAP que acepta ese token, el método de TAP debe devolver una tarea Canceled. Sin embargo, si se solicita la cancelación mientras la operación asincrónica se ejecuta, la operación asincrónica no necesita aceptar la solicitud de cancelación. La tarea devuelta debe finalizar en el estado Canceled solo si la operación termina como resultado de la solicitud de cancelación. Si se solicita la cancelación pero aún se produce un resultado o una excepción, la tarea debe finalizar en el estado RanToCompletion o Faulted. Para los métodos asincrónicos usados por un desarrollador que desea la cancelación ante todo, no tiene que proporcionar una sobrecarga que no acepte un token de cancelación. Para los métodos que no pueden cancelarse, no proporcione sobrecargas que acepten un token de cancelación; esto ayuda a indicar al llamador si el método de destino es realmente cancelable. El código de consumidor que no desea la cancelación puede llamar a un método que acepta un objeto CancellationToken y proporciona None como valor del argumento. None es funcionalmente equivalente al objeto CancellationToken predeterminado.

Algunas operaciones asincrónicas se benefician de proporcionar notificaciones de progreso; se suelen usar para actualizar una interfaz de usuario con información sobre el progreso de la operación asincrónica. En TAP, el progreso se controla a través de una interfaz IProgress<T>, la cual se pasa al método asincrónico como un parámetro normalmente denominado progress. Proporcionar la interfaz de progreso cuando se llama al método asincrónico ayuda a eliminar condiciones de carrera resultantes de un uso incorrecto (es decir, cuando los controladores de eventos registrados incorrectamente después del inicio de la operación pueden perder actualizaciones). Lo que es más importante, la interfaz de progreso admite implementaciones diferentes de progreso, según determina el código usado. Por ejemplo, el código usado puede que desee encargarse solo de la última actualización de progreso, almacenar en búfer todas las actualizaciones, invocar una acción para cada actualización o controlar si se calculan las referencias de la invocación en un subproceso determinado. Todas estas opciones se pueden lograr utilizando otra implementación de la interfaz, personalizada según las necesidades particulares del consumidor. Como ocurre con la cancelación, las implementaciones de TAP deben proporcionar un parámetro IProgress<T> solo si la API admite notificaciones de progreso. Por ejemplo, si el método ReadAsync anteriormente mencionado en este artículo puede informar del progreso intermedio en forma de número de bytes leídos hasta el momento, la devolución de llamada de progreso puede ser una interfaz IProgress<T>:

public Task ReadAsync(byte[] buffer, int offset, int count, 
                      IProgress<long> progress)

Si un método FindFilesAsync devuelve una lista de todos los archivos que satisfacen un patrón particular de búsqueda, la devolución de progreso puede proporcionar una estimación del porcentaje de trabajo completado así como el conjunto de resultados parciales. Puede hacerlo con una tupla:

public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
            string pattern, 
            IProgress<Tuple<double, 
            ReadOnlyCollection<List<FileInfo>>>> progress)

o con un tipo de datos que es específico de la API:

public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
    string pattern, 
    IProgress<FindFilesProgressInfo> progress)

En este último caso, el tipo de datos especial suele tener el sufijo ProgressInfo.

Si las implementaciones de TAP proporcionan sobrecargas que aceptan un parámetro progress, deben permitir que el argumento sea null, en cuyo caso no se notifica ningún progreso. Las implementaciones de TAP deben notificar el progreso al objeto Progress<T> sincrónicamente, lo cual permite al método asincrónico proporcionar rápidamente el progreso y hacer que el consumidor de este determine cómo y dónde es mejor controlar la información. Por ejemplo, la instancia de progreso puede optar por hacerse con las devoluciones de llamada y generar eventos en un contexto capturado de sincronización.

.NET Framework 4.5 proporciona una única implementación de IProgress<T>: Progress<T>. La clase Progress<T> se declara de la forma siguiente:


public class Progress<T> : IProgress<T>
{
    public Progress();
    public Progress(Action<T> handler);
    protected virtual void OnReport(T value);
    public event EventHandler<T> ProgressChanged;
}

Una instancia de Progress<T> expone un evento ProgressChanged, que se provoca cada vez que la operación asincrónica informe de una actualización de progreso. El evento ProgressChanged se genera en el objeto SynchronizationContext que se capturó cuando se creó la instancia de Progress<T>. Si no había ningún contexto de sincronización disponible, se usa un contexto predeterminado destinado al grupo de subprocesos. Los controladores pueden registrarse con este evento. También se puede proporcionar un único controlador al constructor Progress<T> por comodidad, y se comportará como un controlador de eventos para el evento ProgressChanged. Las actualizaciones de progreso se generan de forma asincrónica para evitar retrasar la operación asincrónica mientras los controladores de eventos se ejecutan. Otra implementación IProgress<T> podría elegir aplicar semánticas diferentes.

Si una implementación TAP usa la propiedad CancellationToken y los parámetros IProgress<T> opcionales, podría requerir hasta cuatro sobrecargas:


public Task MethodNameAsync(…);
public Task MethodNameAsync(…, CancellationToken cancellationToken);
public Task MethodNameAsync(…, IProgress<T> progress); 
public Task MethodNameAsync(…, 
    CancellationToken cancellationToken, IProgress<T> progress);

Sin embargo, muchas implementaciones TAP no proporcionan ni la cancelación ni las capacidades de progreso, por lo que requieren un único método:

public Task MethodNameAsync(…);

Si una implementación de TAP admite la cancelación o el progreso pero no ambos, puede proporcionar dos sobrecargas:


public Task MethodNameAsync(…);
public Task MethodNameAsync(…, CancellationToken cancellationToken);

// … or …

public Task MethodNameAsync(…);
public Task MethodNameAsync(…, IProgress<T> progress);

Si una implementación TAP admite la cancelación y el progreso, puede exponer las cuatro sobrecargas. Sin embargo, puede proporcionar solo las dos siguientes:


public Task MethodNameAsync(…);
public Task MethodNameAsync(…, 
    CancellationToken cancellationToken, IProgress<T> progress);

Para compensar las dos combinaciones intermedias que faltan, los desarrolladores pueden pasar la propiedad None o un objeto CancellationToken predeterminado para el parámetro cancellationToken y null para el parámetro progress.

Si se espera que cada uso del método TAP admita cancelación o progreso, puede omitir las sobrecargas que no acepten el parámetro pertinente.

Si se decide exponer varias sobrecargas para conseguir que la cancelación o el progreso sean opcionales, las sobrecargas que no admitan cancelación o progreso deben comportarse como si pasaran None para la cancelación o null para el progreso en la sobrecarga que admite ambas.

Título

Descripción

Asynchronous Programming Patterns

Presenta los tres patrones para realizar las operaciones asincrónicas: el patrón asincrónico basado en tareas (TAP), el modelo de programación asincrónica (APM) y el patrón asincrónico basado en eventos (EAP).

Implementing the Task-based Asynchronous Pattern

Describe cómo implementar el patrón asincrónico basado en tareas (TAP) de tres maneras: mediante los compiladores de C# y Visual Basic de Visual Studio, manualmente o a través de una combinación del compilador y de métodos manuales.

Consuming the Task-based Asynchronous Pattern

Describe cómo se pueden utilizar las tareas y las devoluciones de llamada para conseguir esperas sin bloqueos.

Interop with Other Asynchronous Patterns and Types

Describe cómo usar el patrón asincrónico basado en tareas (TAP) para implementar el modelo de programación asincrónica (APM) y el patrón asincrónico basado en eventos (EAP).

Mostrar: