Modelo de programación asincrónica de tareas
Puede evitar cuellos de botella de rendimiento y mejorar la capacidad de respuesta total de la aplicación mediante la programación asincrónica. Sin embargo, las técnicas tradicionales para escribir aplicaciones asincrónicas pueden resultar complicadas, haciéndolas difícil de escribir, depurar y mantener.
C# admite el enfoque simplificado, la programación asincrónica, que aprovecha la compatibilidad asincrónica en el entorno de ejecución de .NET. El compilador realiza el trabajo difícil que el desarrollador suele realizar y la aplicación conserva una estructura lógica similar al código sincrónico. Como resultado, se obtienen todas las ventajas de la programación asincrónica con una parte del trabajo.
Este tema proporciona información general sobre cuándo y cómo utilizar la programación asincrónica e incluye vínculos que admiten temas con detalles y ejemplos.
La asincronía mejora la capacidad de respuesta
La asincronía es esencial para actividades que pueden producir bloqueos, por ejemplo, el acceso a Internet. Tener acceso a un recurso web a veces es lento o va retrasado. Si tal actividad queda bloqueada en un proceso sincrónico, toda la aplicación deberá esperar. En un proceso asincrónico, la aplicación puede continuar con otro trabajo que no dependa del recurso web hasta que finaliza la tarea que puede producir bloqueos.
En la tabla siguiente se muestran las áreas típicas donde la programación asincrónica mejora su capacidad de respuesta. Las API enumeradas desde .NET y Windows Runtime contienen métodos que admiten la programación asincrónica.
Área de aplicación | Tipos de .NET con métodos asincrónicos | Tipos de Windows Runtime con métodos asincrónicos |
---|---|---|
Acceso web | HttpClient | Windows.Web.Http.HttpClient SyndicationClient |
Trabajar con archivos | JsonSerializer StreamReader StreamWriter XmlReader XmlWriter |
StorageFile |
Trabajar con imágenes | MediaCapture BitmapEncoder BitmapDecoder |
|
Programar WCF | Operaciones sincrónicas y asincrónicas |
La asincronía es especialmente valiosa para aquellas aplicaciones que obtienen acceso al subproceso de interfaz de usuario, ya que todas las actividades relacionadas con la interfaz de usuario normalmente comparten un único subproceso. Si se bloquea un proceso en una aplicación sincrónica, se bloquean todos. La aplicación deja de responder y puede que se piense que se ha producido un error cuando en realidad la aplicación está esperando.
Cuando se usan métodos asincrónicos, la aplicación continúa respondiendo a la interfaz de usuario. Puede cambiar el tamaño o minimizar una ventana, por ejemplo, o puede cerrar la aplicación si no desea esperar a que finalice.
El enfoque basado en asincrónico agrega el equivalente de una transmisión automática a la lista de opciones entre las que puede elegir al diseñar operaciones asincrónicas. Es decir, obtiene todas las ventajas de la programación asincrónica tradicional pero con mucho menos trabajo de desarrollador.
Los métodos asincrónicos son fáciles de escribir
Las palabras clave async y await en C# son fundamentales en la programación asincrónica. Con esas dos palabras clave, se pueden usar los recursos de .NET Framework, .NET Core o Windows Runtime para crear un método asincrónico casi tan fácilmente como se crea un método sincrónico. Los métodos asincrónicos que se definen mediante la palabra clave async
se denominan métodos asincrónicos.
En el ejemplo siguiente se muestra un método asincrónico. Prácticamente todos los elementos del código deberían resultarle familiares.
Puede encontrar un ejemplo completo de Windows Presentation Foundation (WPF) disponible para su descarga en el artículo Programación asincrónica con async y await en C#.
public async Task<int> GetUrlContentLengthAsync()
{
using var client = new HttpClient();
Task<string> getStringTask =
client.GetStringAsync("https://learn.microsoft.com/dotnet");
DoIndependentWork();
string contents = await getStringTask;
return contents.Length;
}
void DoIndependentWork()
{
Console.WriteLine("Working...");
}
Del ejemplo anterior puede obtener información sobre varios procedimientos. Comience con la firma del método. Incluye el modificador async
. El tipo de valor devuelto es Task<int>
(vea la sección "Tipos de valor devuelto" para obtener más opciones). El nombre del método termina en Async
. En el cuerpo del método, GetStringAsync
devuelve un elemento Task<string>
. Esto significa que, cuando se aplica await
a la tarea, se obtiene un elemento string
(contents
). Antes de la espera de la tarea, puede realizar otras acciones que no se basen en el elemento string
de GetStringAsync
.
Preste mucha atención al operador await
. Suspende a GetUrlContentLengthAsync
:
GetUrlContentLengthAsync
no puede continuar hasta que se completegetStringTask
.- Mientras tanto, el control vuelve al autor de la llamada de
GetUrlContentLengthAsync
. - Aquí se reanuda el control cuando se completa
getStringTask
. - Después, el operador
await
recupera el resultadostring
degetStringTask
.
La instrucción return especifica un resultado entero. Los métodos que están a la espera de GetUrlContentLengthAsync
recuperan el valor de longitud.
Si GetUrlContentLengthAsync
no hay ningún trabajo que se pueda hacer entre llamar a GetStringAsync
y esperar a su finalización, se puede simplificar el código llamando y esperando en la siguiente instrucción única.
string contents = await client.GetStringAsync("https://learn.microsoft.com/dotnet");
Las siguientes características resumen lo que hace que el ejemplo anterior sea un método asincrónico:
Method Signature incluye un modificador
async
.El nombre de un método asincrónico, por convención, finaliza con un sufijo “Async”.
El tipo de valor devuelto es uno de los tipos siguientes:
- Task<TResult> si el método tiene una instrucción return en la que el operando tiene el tipo
TResult
. - Task si el método no tiene ninguna instrucción return ni tiene una instrucción return sin operando.
void
si está escribiendo un controlador de eventos asincrónicos.- Cualquier otro tipo que tenga un método
GetAwaiter
.
Para obtener más información, consulte la sección Tipos de valor devuelto y parámetros.
- Task<TResult> si el método tiene una instrucción return en la que el operando tiene el tipo
El método normalmente incluye al menos una expresión
await
, que marca un punto en el que el método no puede continuar hasta que se completa la operación asincrónica en espera. Mientras tanto, se suspende el método y el control vuelve al llamador del método. La sección siguiente de este tema muestra lo que sucede en el punto de suspensión.
En métodos asincrónicos, se utilizan las palabras clave y los tipos proporcionados para indicar lo que se desea hacer y el compilador realiza el resto, incluido el seguimiento de qué debe ocurrir cuando el control vuelve a un punto de espera en un método suspendido. Algunos procesos de rutina, tales como bucles y control de excepciones, pueden ser difíciles de controlar en código asincrónico tradicional. En un método asincrónico, se pueden escribir estos elementos como se haría en una solución sincrónica y se resuelve este problema.
Para obtener más información sobre la asincronía en versiones anteriores de .NET Framework, vea TPL y la programación asincrónica tradicional de .NET Framework.
Lo que ocurre en un método asincrónico
Lo más importante de entender en la programación asincrónica es cómo el flujo de control pasa de un método a otro. El diagrama siguiente lo guía en el proceso:
Los números del diagrama se corresponden con los pasos siguientes, que se inician cuando un método de llamada llama al método asincrónico.
Un método de llamada llama al método asincrónico
GetUrlContentLengthAsync
y lo espera.GetUrlContentLengthAsync
crea una instancia HttpClient y llama al método asincrónico GetStringAsync para descargar el contenido de un sitio web como una cadena.Sucede algo en
GetStringAsync
que suspende el progreso. Quizás debe esperar a un sitio web para realizar la descarga o alguna otra actividad de bloqueo. Para evitar el bloqueo de recursos,GetStringAsync
genera un control a su llamador,GetUrlContentLengthAsync
.GetStringAsync
devuelve un elemento Task<TResult>, dondeTResult
es una cadena yGetUrlContentLengthAsync
asigna la tarea a la variablegetStringTask
. La tarea representa el proceso actual para la llamada aGetStringAsync
, con el compromiso de generar un valor de cadena real cuando se completa el trabajo.Debido a que
getStringTask
no se ha esperado,GetUrlContentLengthAsync
puede continuar con otro trabajo que no dependa del resultado final deGetStringAsync
. Ese trabajo se representa mediante una llamada al método sincrónicoDoIndependentWork
.DoIndependentWork
es un método sincrónico que funciona y vuelve al llamador.GetUrlContentLengthAsync
se ha quedado sin el trabajo que se puede realizar sin un resultado degetStringTask
. Después,GetUrlContentLengthAsync
desea calcular y devolver la longitud de la cadena descargada, pero el método no puede calcular ese valor hasta que el método tiene la cadena.Por consiguiente,
GetUrlContentLengthAsync
utiliza un operador await para suspender el progreso y producir el control al método que llamóGetUrlContentLengthAsync
.GetUrlContentLengthAsync
devuelveTask<int>
al llamador. La tarea representa una sugerencia para generar un resultado entero que es la longitud de la cadena descargada.Nota
Si
GetStringAsync
(y, por tanto,getStringTask
) se completa antes de queGetUrlContentLengthAsync
lo espere, el control permanece enGetUrlContentLengthAsync
. El gasto de suspensión y de regresar aGetUrlContentLengthAsync
se desperdiciaría si el proceso asincrónicogetStringTask
al que se llama ya se ha completado yGetUrlContentLengthAsync
no tiene que esperar el resultado final.Dentro del método de llamada, el patrón de procesamiento continúa. El llamador puede hacer otro trabajo que no depende del resultado de
GetUrlContentLengthAsync
antes de esperar ese resultado, o es posible que el llamador se espere inmediatamente. El método de llamada espera aGetUrlContentLengthAsync
, yGetUrlContentLengthAsync
espera aGetStringAsync
.GetStringAsync
completa y genera un resultado de la cadena. La llamada aGetStringAsync
no devuelve el resultado de la cadena de la manera que cabría esperar. (Recuerde que el método ya devolvió una tarea en el paso 3). En su lugar, el resultado de la cadena se almacena en la tarea que representa la finalización del método,getStringTask
. El operador await recupera el resultado degetStringTask
. La instrucción de asignación asigna el resultado recuperado acontents
.Cuando
GetUrlContentLengthAsync
tiene el resultado de la cadena, el método puede calcular la longitud de la cadena. El trabajo deGetUrlContentLengthAsync
también se completa y el controlador de eventos que espera se puede reanudar. En el ejemplo completo del final de este tema, puede comprobar que el controlador de eventos recupera e imprime el valor de resultado de longitud. Si no está familiarizado con la programación asincrónica, reserve un minuto para ver la diferencia entre el comportamiento sincrónico y asincrónico. Un método sincrónico devuelve cuando se completa su trabajo (paso 5), pero un método asincrónico devuelve un valor de tarea cuando se suspende el trabajo (pasos 3 y 6). Cuando el método asincrónico completa finalmente el trabajo, se marca la tarea como completa y el resultado, si existe, se almacena en la tarea.
Métodos asincrónicos de API
Tal vez se pregunte dónde encontrar métodos como GetStringAsync
que sean compatibles con la programación asincrónica. .NET Framework 4.5 o versiones posteriores y .NET Core contienen muchos miembros que trabajan con async
y await
. Puede reconocerlos por el sufijo "Async" que se anexa al nombre del miembro y por su tipo de valor devuelto Task o Task<TResult>. Por ejemplo, la clase System.IO.Stream
contiene métodos como CopyToAsync, ReadAsync y WriteAsync junto con los métodos sincrónicos CopyTo, Read y Write.
Windows Runtime también contiene muchos métodos que puede utilizar con async
y await
en Aplicaciones Windows. Para más información, consulte Subprocesamiento y programación asincrónica para el desarrollo con UWP, y Programación asincrónica (aplicaciones de la Tienda Windows) y Guía de inicio rápido: Llamadas a API asincrónicas en C# o Visual Basic si usa versiones anteriores de Windows Runtime.
Subprocesos
La intención de los métodos Async es ser aplicaciones que no pueden producir bloqueos. Una expresión await
en un método asincrónico no bloquea el subproceso actual mientras la tarea esperada se encuentra en ejecución. En vez de ello, la expresión declara el resto del método como una continuación y devuelve el control al llamador del método asincrónico.
Las palabras clave async
y await
no hacen que se creen subprocesos adicionales. Los métodos Async no requieren multithreading, ya que un método asincrónico no se ejecuta en su propio subproceso. El método se ejecuta en el contexto de sincronización actual y ocupa tiempo en el subproceso únicamente cuando el método está activo. Puede utilizar Task.Run para mover el trabajo enlazado a la CPU a un subproceso en segundo plano, pero un subproceso en segundo plano no ayuda con un proceso que solo está esperando a que los resultados estén disponibles.
El enfoque basado en asincrónico en la programación asincrónica es preferible a los enfoques existentes en casi todos los casos. En concreto, este enfoque es mejor que la clase BackgroundWorker para las operaciones enlazadas a E/S porque el código es más sencillo y no se tiene que proteger contra las condiciones de carrera. Junto con el método Task.Run, la programación asincrónica es mejor que BackgroundWorker para las operaciones enlazadas a la CPU, ya que la programación asincrónica separa los detalles de coordinación en la ejecución del código del trabajo que Task.Run
transfiere al grupo de subprocesos.
async y await
Si especifica que un método es un método asincrónico mediante el modificador async, habilita las dos funciones siguientes.
El método asincrónico marcado puede utilizar await para designar puntos de suspensión. El operador
await
indica al compilador que el método asincrónico no puede continuar pasado ese punto hasta que se complete el proceso asincrónico aguardado. Mientras tanto, el control devuelve al llamador del método asincrónico.La suspensión de un método asincrónico en una expresión
await
no constituye una salida del método y los bloquesfinally
no se ejecutan.El método asincrónico marcado sí se puede esperar por los métodos que lo llaman.
Un método asincrónico normalmente contiene una o más apariciones de un operador await
, pero la ausencia de expresiones await
no causa errores de compilación. Si un método asincrónico no usa un operador await
para marcar el punto de suspensión, se ejecuta como un método sincrónico, a pesar del modificador async
. El compilador detecta una advertencia para dichos métodos.
async
y await
son palabras clave contextuales. Para mayor información y ejemplos, vea los siguientes temas:
Tipos de valor devuelto y parámetros
Un método asincrónico suele devolver Task o Task<TResult>. Dentro de un método asincrónico, se aplica un operador await
a una tarea que devuelve una llamada a otro método asincrónico.
Puede especificar Task<TResult> como el tipo de valor devuelto si el método contiene una instrucción return
en la que se especifica un operando de tipo TResult
.
Puede utilizar Task como tipo de valor devuelto si el método no tiene instrucción return o tiene una instrucción return que no devuelve un operando.
También puede especificar cualquier otro tipo de valor devuelto, siempre que dicho tipo incluya un método GetAwaiter
. Un ejemplo de este tipo es ValueTask<TResult>. Está disponible en el paquete NuGet System.Threading.Tasks.Extension.
En el ejemplo siguiente se muestra cómo declarar y llamar a un método que devuelve Task<TResult> o Task:
async Task<int> GetTaskOfTResultAsync()
{
int hours = 0;
await Task.Delay(0);
return hours;
}
Task<int> returnedTaskTResult = GetTaskOfTResultAsync();
int intResult = await returnedTaskTResult;
// Single line
// int intResult = await GetTaskOfTResultAsync();
async Task GetTaskAsync()
{
await Task.Delay(0);
// No return statement needed
}
Task returnedTask = GetTaskAsync();
await returnedTask;
// Single line
await GetTaskAsync();
Cada tarea devuelta representa el trabajo en curso. Una tarea encapsula la información sobre el estado del proceso asincrónico y, finalmente, el resultado final del proceso o la excepción que el proceso provoca si no tiene éxito.
Un método asincrónico también puede tener un tipo de valor devuelto void
. Este tipo de valor devuelto se utiliza principalmente para definir controladores de eventos, donde se requiere un tipo de valor devuelto void
. Los controladores de eventos asincrónicos sirven a menudo como punto de partida para programas asincrónicos.
No se puede esperar a un método asincrónico que tenga un tipo de valor devuelto void
y el llamador de un método con tipo de valor devuelto void no puede capturar ninguna excepción producida por este.
Un método asincrónico no puede declarar ningún parámetro in, ref o out, pero el método puede llamar a los métodos que tienen estos parámetros. De forma similar, un método asincrónico no puede devolver un valor por referencia, aunque puede llamar a métodos con valores devueltos ref.
Para obtener más información y ejemplos, vea Tipos de valor devueltos asincrónicos (C#).
Las API asincrónicas en la programación de Windows Runtime tienen uno de los siguientes tipos de valor devuelto, que son similares a las tareas:
- IAsyncOperation<TResult>, lo que equivale a Task<TResult>
- IAsyncAction, lo que equivale a Task
- IAsyncActionWithProgress<TProgress>
- IAsyncOperationWithProgress<TResult,TProgress>
Convención de nomenclatura
Por convención, los nombres de los métodos que devuelven tipos que suelen admitir "await" (por ejemplo, Task
, Task<T>
, ValueTask
y ValueTask<T>
) deben terminar por "Async". Los nombres de los métodos que inician operaciones asincrónicas, pero que no devuelven un tipo que admite "await", no deben terminar en "Async", pero pueden empezar por "Begin", "Start" o cualquier otro verbo para sugerir que este método no devuelve ni genera el resultado de la operación.
Puede ignorar esta convención cuando un evento, clase base o contrato de interfaz sugieren un nombre diferente. Por ejemplo, no se debería cambiar el nombre de los controladores de eventos, tales como OnButtonClick
.
Artículos relacionados (Visual Studio)
Título | Descripción |
---|---|
Procedimiento para realizar varias solicitudes web en paralelo con async y await (C#) | Demuestra cómo comenzar varias tareas al mismo tiempo. |
Tipos de valor devueltos asincrónicos (C#) | Muestra los tipos que los métodos asincrónicos pueden devolver y explica cuándo es apropiado cada uno de ellos. |
Cancelación de tareas con un token de cancelación como mecanismo de señalización. | Muestra cómo agregar la siguiente funcionalidad a la solución asincrónica: - Cancelación de una lista de tareas (C#) - Cancelación de tareas asincrónicas tras un período de tiempo (C#) - Iniciar varias tareas asincrónicas y procesarlas a medida que se completan (C#) |
Usar Async en acceso a archivos (C#) | Enumera y demuestra los beneficios de usar async y await para obtener acceso a archivos. |
Modelo asincrónico basado en tareas (TAP) | Describe un modelo asincrónico, el patrón se basa en los tipos Task y Task<TResult>. |
Vídeos de Async en Channel 9 | Proporciona vínculos a una serie de vídeos sobre programación asincrónica. |