Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Puede evitar cuellos de botella de rendimiento y mejorar la capacidad de respuesta general de la aplicación mediante la programación asincrónica. Sin embargo, las técnicas tradicionales para escribir aplicaciones asincrónicas pueden ser complicadas, lo que dificulta la escritura, depuración y mantenimiento.
C# admite el enfoque simplificado, la programación asincrónica, que usa compatibilidad asincrónica en el entorno de ejecución de .NET. El compilador realiza el trabajo difícil que el desarrollador usó para realizar y la aplicación conserva una estructura lógica similar al código sincrónico. Como resultado, obtendrá todas las ventajas de la programación asincrónica con una fracción del esfuerzo.
En este artículo se proporciona información general sobre cuándo y cómo usar la programación asincrónica e incluye vínculos a otros artículos que contienen detalles y ejemplos.
Async mejora la capacidad de respuesta
La asincronía es esencial para las actividades que pueden bloquearse, como el acceso web. El acceso a un recurso web a veces es lento o retrasado. Si esta actividad está bloqueada en un proceso sincrónico, toda la aplicación debe esperar. En un proceso asincrónico, la aplicación puede continuar con otro trabajo que no depende del recurso web hasta que finalice la tarea de bloqueo potencial.
En la tabla siguiente se muestran las áreas típicas en las que la programación asincrónica mejora la capacidad de respuesta. Las API enumeradas de .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 |
|
| Programación de WCF | Operaciones sincrónicas y asincrónicas |
La asincronía resulta especialmente valiosa para las aplicaciones que acceden al subproceso de la interfaz de usuario porque toda la actividad relacionada con la interfaz de usuario suele compartir un subproceso. Si algún proceso está bloqueado en una aplicación sincrónica, se bloquean todos. La aplicación deja de responder y puede concluir que se produjo un error cuando, en su lugar, está esperando.
Cuando se usan métodos asincrónicos, la aplicación sigue 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, obtendrá todas las ventajas de la programación asincrónica tradicional, pero con mucho menos esfuerzo del desarrollador.
Los métodos asincrónicos son fáciles de escribir
Las palabras clave async y await en C# son el núcleo de la programación asincrónica. Con esas dos palabras clave, puede usar recursos en .NET Framework, .NET Core o Windows Runtime para crear un método asincrónico casi tan fácilmente como crear un método sincrónico. Los métodos asincrónicos que defina mediante la palabra clave async se conocen como métodos asincrónicos .
En el ejemplo siguiente se muestra un método asincrónico. Casi todo en el código debería parecerle familiar.
Puede encontrar un ejemplo completo de Windows Presentation Foundation (WPF) disponible para su descarga desde Programación asincrónica utilizando 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...");
}
Se pueden aprender varias prácticas del ejemplo anterior. Comience con la firma del método. Incluye el modificador async. El tipo de valor devuelto es Task<int> (consulte 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 Task<string>. Esto significa que cuando await la tarea, recibes un 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:
-
GetUrlContentLengthAsyncno 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
awaitrecupera el resultadostringdegetStringTask.
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 tiene ningún trabajo que pueda realizar entre llamar a GetStringAsync y esperar su finalización, puede simplificar el código llamando y esperando en una sola instrucción.
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:
La firma del método incluye un modificador
async.El nombre de un método asincrónico, por convención, termina con un sufijo "Async".
El tipo de valor devuelto es uno de los siguientes tipos:
-
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 o tiene una instrucción return sin operando.
-
voidsi está escribiendo un controlador de eventos asincrónico. - Cualquier otro tipo que tenga un método
GetAwaiter.
Para obtener más información, consulte la sección Tipos y parámetros devueltos.
-
Task<TResult> si el método tiene una instrucción return en la que el operando tiene el tipo
Normalmente, el método incluye al menos una expresión
await, que marca un punto en el que el método no puede continuar hasta que se complete la operación asincrónica esperada. Mientras tanto, se suspende el método y el control vuelve al llamador del método. En la siguiente sección de este artículo se muestra lo que sucede en el punto de suspensión.
En los métodos asincrónicos, se usan las palabras clave y los tipos proporcionados para indicar lo que desea hacer y el compilador hace el resto, incluido realizar un seguimiento de lo que debe ocurrir cuando el control vuelve a un punto de espera en un método suspendido. Algunos procesos rutinarios, 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 escriben estos elementos tanto como lo haría en una solución sincrónica y se resuelve el problema.
Para obtener más información sobre la asincronía en versiones anteriores de .NET Framework, consulte TPL y la programación asincrónica tradicional de .NET Framework.
¿Qué ocurre en un método asincrónico?
Lo más importante que hay que comprender en la programación asincrónica es cómo el flujo de control se mueve del método al método . El siguiente diagrama le lleva a través del proceso:
Los números del diagrama corresponden a los pasos siguientes, iniciados cuando un método de llamada llama al método asincrónico.
Un método de llamada llama al método asincrónico
GetUrlContentLengthAsyncy lo espera.GetUrlContentLengthAsynccrea una instancia de HttpClient y llama al método asincrónico GetStringAsync para descargar el contenido de un sitio web como una cadena.Sucede algo en
GetStringAsyncque suspende su progreso. Quizás debe esperar a que un sitio web se descargue o alguna otra actividad de bloqueo. Para evitar el bloqueo de recursos,GetStringAsynccede el control a su llamador,GetUrlContentLengthAsync.GetStringAsyncdevuelve un Task<TResult>, dondeTResultes una cadena yGetUrlContentLengthAsyncasigna 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.Dado que
getStringTaskaún no se ha aguardado,GetUrlContentLengthAsyncpuede continuar con otro trabajo que no dependa del resultado final deGetStringAsync. Ese trabajo se representa mediante una llamada al método sincrónicoDoIndependentWork.DoIndependentWorkes un método síncrono que realiza su trabajo y vuelve a quien lo llama.GetUrlContentLengthAsyncse queda sin más tareas que pueda realizar sin un resultado degetStringTask.GetUrlContentLengthAsyncsiguiente quiere calcular y devolver la longitud de la cadena descargada, pero el método no puede calcular ese valor hasta que el método tenga la cadena.Por consiguiente,
GetUrlContentLengthAsyncutiliza un operador await para suspender el progreso y producir el control al método que llamóGetUrlContentLengthAsync.GetUrlContentLengthAsyncdevuelve unTask<int>al autor de la llamada. La tarea representa una promesa de generar un resultado entero que es la longitud de la cadena descargada.Nota:
Si
GetStringAsync(y, por tanto,getStringTask) se completa antes de queGetUrlContentLengthAsynclo espere, el control permanece enGetUrlContentLengthAsync. El coste de suspender y luego regresar aGetUrlContentLengthAsyncsería en vano si el proceso asincrónicogetStringTaskllamado ya está completo yGetUrlContentLengthAsyncno necesita 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
GetUrlContentLengthAsyncantes de esperar ese resultado, o es posible que el llamador se espere inmediatamente. El método de llamada espera aGetUrlContentLengthAsync, yGetUrlContentLengthAsyncespera aGetStringAsync.GetStringAsynccompleta y genera un resultado de la cadena. La llamada aGetStringAsyncno devuelve el resultado en cadena de la manera que podrías 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
GetUrlContentLengthAsynctiene el resultado de la cadena, el método puede calcular la longitud de la cadena. El trabajo deGetUrlContentLengthAsynctambién se completa y el controlador de eventos que espera se puede reanudar. En el ejemplo completo al final del artículo, puede confirmar que el controlador de eventos recupera e imprime el valor del resultado de longitud. Si no está familiarizado con la programación asincrónica, dedique un minuto a tener en cuenta 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 su trabajo (pasos 3 y 6). Cuando el método asincrónico finaliza su trabajo, la tarea se marca como completada y el resultado, si existe, se almacena en la tarea.
Métodos asincrónicos de API
Es posible que se pregunte dónde encontrar métodos como GetStringAsync que admiten la programación asincrónica. .NET Framework 4.5 o posterior y .NET Core contienen muchos miembros que funcionan con async y await. Puede reconocerlos por el sufijo "Async" anexado al nombre de miembro y por su tipo de valor devuelto de Task o Task<TResult>. Por ejemplo, la clase System.IO.Stream contiene métodos como CopyToAsync, ReadAsyncy WriteAsync junto con los métodos sincrónicos CopyTo, Ready Write.
Windows Runtime también contiene muchos métodos que puedes usar con async y await en aplicaciones de 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
Los métodos asincrónicos están diseñados para ser operaciones sin bloqueo. Una expresión await en un método asincrónico no bloquea el subproceso actual mientras se ejecuta la tarea esperada. En su lugar, la expresión registra el resto del método como una continuación y devuelve el control al autor de la llamada del método asincrónico.
Las async y await palabras clave no hacen que se creen subprocesos adicionales. Los métodos asincrónicos no requieren multithreading porque 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 usa tiempo en el subproceso solo cuando el método está activo. Puede usar 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 espera a que los resultados estén disponibles.
El enfoque basado en 'async' para 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. En combinación 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 de 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 asincrono usando el modificador asincrono, habilita las dos funcionalidades siguientes.
El método asincrónico marcado puede utilizar await para designar puntos de suspensión. El operador
awaitindica al compilador que el método asincrónico no puede continuar después de ese punto hasta que se complete el proceso asincrónico esperado. 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
awaitno significa que el método haya finalizado, y los bloquesfinallyno se ejecutarán.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 varias apariciones de un operador await, pero la ausencia de expresiones de await no produce un error del compilador. Si un método asincrónico no usa un operador de await para marcar un punto de suspensión, el método se ejecuta como un método sincrónico, a pesar del modificador async. El compilador emite una advertencia para estos métodos.
async y await son palabras clave contextuales. Para obtener más información y ejemplos, consulte los siguientes artículos:
Tipos de valor devuelto y parámetros
Normalmente, un método asincrónico devuelve un Task o un Task<TResult>. Dentro de un método asincrónico, se aplica un operador await a una tarea que se devuelve de una llamada a otro método asincrónico.
Especifique Task<TResult> como tipo de valor devuelto si el método contiene una instrucción return que especifica un operando de tipo TResult.
Se usa Task como tipo de valor devuelto si el método no tiene ninguna instrucción return o tiene una instrucción return que no devuelve un operando.
También puede especificar cualquier otro tipo de valor devuelto, si el tipo incluye un GetAwaiter método .
ValueTask<TResult> es un ejemplo de este tipo. 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 un Task<TResult> o un 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 información sobre el estado del proceso asincrónico y, finalmente, el resultado final del proceso o la excepción que genera el proceso si no se realiza correctamente.
Un método asincrónico también puede tener un tipo de valor devuelto void. Este tipo de valor devuelto se usa principalmente para definir controladores de eventos, donde se requiere un tipo de valor devuelto void. Los controladores de eventos asincrónicos suelen servir 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. Del mismo modo, 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>, que corresponde a Task<TResult>
- IAsyncAction, que corresponde a Task
- IAsyncActionWithProgress<TProgress>
- IAsyncOperationWithProgress<TResult,TProgress>
Convención de nomenclatura
Por convención, los métodos que devuelven tipos comúnmente esperables (por ejemplo, Task, Task<T>, ValueTask, ValueTask<T>) deberían tener nombres que terminen con "Async". Los métodos que inician una operación asincrónica, pero que no devuelven un tipo esperado no deben tener nombres que terminen con "Async", pero podrían comenzar con "Begin", "Start" o algún otro verbo para sugerir que este método no devuelve ni produce el resultado de la operación.
Puede omitir la convención en la que un evento, una clase base o un contrato de interfaz sugieren un nombre diferente. Por ejemplo, no debe cambiar el nombre de los controladores de eventos comunes, como OnButtonClick.
Artículos relacionados (Visual Studio)
| Título | Descripción |
|---|---|
| Cómo realizar varias solicitudes web en paralelo mediante async y await (C#) | Muestra cómo iniciar varias tareas al mismo tiempo. |
| Tipos de valor devueltos asincrónicos (C#) | Ilustra los tipos que pueden devolver los métodos asincrónicos y explica cuándo es adecuado cada tipo. |
| Cancelar 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: - Cancelar una lista de tareas (C#) - Cancelar tareas después de un período de tiempo (C#) - Iniciar varias tareas asincrónicas y procesarlas a medida que se completan (C#) |
| Uso de async para el acceso a archivos (C#) | Enumera y muestra las ventajas de usar async y await para acceder a los archivos. |
| patrón asincrónico basado en tareas (TAP) | Describe un patrón 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 varios vídeos sobre la programación asincrónica. |