Usar métodos asincrónicos en ASP.NET 4.5
Por Rick Anderson
Este tutorial le enseñará los conceptos básicos de la creación de una aplicación asincrónica de ASP.NET Web Forms mediante Visual Studio Express 2012 para Web, que es una versión gratuita de Microsoft Visual Studio. También puede usar Visual Studio 2012. Este tutorial incluye las siguientes secciones.
- Cómo se procesan las solicitudes en el Grupo de subprocesos
- Elegir métodos sincrónicos o asincrónicos
- Aplicación de ejemplo
- Página sincrónica de Gizmos
- Crear una página de Gizmos asincrónica
- Realizar varias operaciones en paralelo
- Usar un token de cancelación
- Configuración del servidor para llamadas de servicio web de alta simultaneidad o latencia
Se proporciona un ejemplo completo para este tutorial en
https://github.com/RickAndMSFT/Async-ASP.NET/ en el sitio de GitHub.
ASP.NET 4.5 Web Pages en combinación con .NET 4.5 permite registrar métodos asincrónicos que devuelven un objeto de tipo Tarea. .NET Framework 4 introdujo un concepto de programación asincrónico denominado Tarea y ASP.NET 4.5 admite Tarea. Las tareas se representan mediante el tipo de Tarea y los tipos relacionados en el espacio de nombres System.Threading.Tasks. .NET Framework 4.5 crea sobre esta compatibilidad asincrónica con las palabras clave await y async que hacen que el trabajo con objetos Tarea sea mucho menos complejo que los enfoques asincrónicos anteriores. La palabra clave await es abreviatura sintáctica para indicar que un fragmento de código debe esperar de forma asincrónica en algún otro fragmento de código. La palabra clave async representa una sugerencia que puede usar para marcar métodos como asincrónicos basados en tareas. La combinación de await, asyncy el objetoTarea facilita mucho la escritura de código asincrónico en .NET 4.5. El nuevo modelo para métodos asincrónicos se denomina Patrón asincrónico basado en tareas (PAT). En este tutorial se supone que está familiarizado con la programación asincrónica mediante palabras clave await y async y el espacio de nombres Tarea.
Para obtener más información sobre el uso de las palabras clave await y async y el espacio de nombres Tarea, vea las siguientes referencias.
- Notas del producto: Asincronía en .NET
- Preguntas más frecuentes sobre Async/Await
- Programación asincrónica de Visual Studio
Cómo se procesan las solicitudes en el grupo de subprocesos
En el servidor web, .NET Framework mantiene un grupo de subprocesos que se utilizan para dar servicio a las solicitudes de ASP.NET. Cuando se recibe una solicitud, se envía un subproceso del grupo para procesarla. Si la solicitud se procesa de manera sincrónica, el subproceso está ocupado mientras la procesa, de modo que ese subproceso no puede prestar servicio a otra solicitud.
Esto podría no dar lugar a ningún problema, porque se puede crear un grupo de subprocesos lo bastante grande para alojar numerosos subprocesos ocupados. Sin embargo, el número de subprocesos del grupo de subprocesos es limitado (el máximo predeterminado para .NET 4.5 es de 5 000). En aplicaciones grandes con alta simultaneidad de solicitudes de larga duración, es posible que todos los subprocesos disponibles estén ocupados. Esta situación se denomina colapso de los subprocesos. Cuando se llega a esta situación, el servidor web pone las solicitudes en cola. Si la cola de solicitudes se llena, el servidor web rechaza las solicitudes y muestra el estado HTTP 503 (Servidor muy ocupado). El grupo de subprocesos CLR tiene limitaciones en las inyecciones de subprocesos nuevos. Si la simultaneidad tiene ráfagas (es decir, el sitio web puede obtener repentinamente un gran número de solicitudes) y todos los subprocesos de solicitud disponibles están ocupados debido a las llamadas de backend con una latencia alta, la tasa limitada de inyección de subprocesos puede hacer que la aplicación responda muy mal. Además, cada nuevo subproceso agregado al grupo de subprocesos tiene sobrecarga (por ejemplo, 1 MB de memoria de pila). Una aplicación web que usa métodos sincrónicos para atender llamadas de alta latencia en las que el grupo de subprocesos crece hasta el máximo predeterminado de .NET 4.5 de 5 000 subprocesos lo que consumiría aproximadamente 5 GB más memoria que una aplicación capaz de atender las mismas solicitudes mediante métodos asincrónicos y solo 50 subprocesos. Cuando realiza un trabajo asincrónico, no siempre usa un subproceso. Por ejemplo, al realizar una solicitud de servicio web asincrónica, ASP.NET no usará ningún subproceso entre la llamada al método async y await . El uso del grupo de subprocesos para atender las solicitudes con alta latencia puede provocar una gran superficie de memoria y un uso deficiente del hardware del servidor.
Procesar solicitudes asincrónicas
En las aplicaciones web que ven un gran número de solicitudes simultáneas al inicio o tienen una carga de ráfaga (donde la simultaneidad aumenta repentinamente), realizar llamadas de servicio web asincrónicas aumentará la capacidad de respuesta de la aplicación. Una solicitud asincrónica tarda el mismo tiempo en procesarse que una sincrónica. Por ejemplo, si una solicitud realiza una llamada de servicio web que tarda dos segundos en completarse, la solicitud tardará dos segundos con independencia de que se procese de manera sincrónica o asincrónica. Sin embargo, durante una llamada asincrónica, el subproceso no está ocupado para responder a otras solicitudes mientras espera que se complete la primera. Por lo tanto, las solicitudes asincrónicas impiden el crecimiento del grupo de subprocesos y las colas de solicitudes cuando hay muchas solicitudes simultáneas que invocan operaciones de larga duración.
Elegir métodos sincrónicos o asincrónicos
En esta sección se muestran las directrices para decidir cuándo utilizar métodos sincrónicos o asincrónicos. Se trata de meras directrices; debe estudiar individualmente cada aplicación para determinar si los métodos asincrónicos ayudan a mejorar el rendimiento.
En general, use métodos sincrónicos para las condiciones siguientes:
- Las operaciones son simples o de ejecución breve.
- La simplicidad es más importante que la eficacia.
- Las operaciones son principalmente operaciones de la CPU y no operaciones que requieren una gran sobrecarga del disco o de la red. El uso de métodos asincrónicos en operaciones relacionadas con la CPU no proporciona ninguna ventaja y da lugar a mayor sobrecarga.
En general, use métodos asincrónicos para las condiciones siguientes:
Está llamando a servicios que se pueden consumir a través de métodos asincrónicos y usa .NET 4.5 o posterior.
Las operaciones están relacionadas con la red o con E/S y no con la CPU.
El paralelismo es más importante que la simplicidad de código.
Desea proporcionar un mecanismo que permita a los usuarios cancelar una solicitud de ejecución prolongada.
Cuando la ventaja de cambiar subprocesos supera el costo del modificador de contexto. En general, debe realizar un método asincrónico si el método sincrónico mantiene ocupado el subproceso de solicitud ASP.NET mientras no realiza ningún trabajo. Al realizar la llamada asincrónica, el subproceso de solicitud de ASP.NET no está ocupado sin trabajar mientras espera a que se complete la solicitud de servicio web.
Las pruebas muestran que las operaciones ocupadas constituyen un cuello de botella para el rendimiento del sitio y que IIS puede prestar servicio a más solicitudes si se utilizan métodos asincrónicos para estas llamadas que están ocupadas.
En el ejemplo descargable se muestra cómo utilizar con eficacia los métodos asincrónicos. El ejemplo proporcionado se diseñó para proporcionar una demostración sencilla de la programación asincrónica en ASP.NET 4.5. El ejemplo no está pensado para ser una arquitectura de referencia para la programación asincrónica en ASP.NET. El programa de ejemplo llama a ASP.NET Web API que, a su vez, llama a Task.Delay para simular llamadas de servicio web de larga duración. Pocas aplicaciones de producción muestran ventajas tan evidentes del uso de métodos asincrónicos.
Pocas aplicaciones exigen que todos los métodos sean asincrónicos. Con frecuencia, basta con convertir algunos métodos sincrónicos en métodos asincrónicos para obtener la máxima eficacia para la cantidad de trabajo requerida.
Aplicación de ejemplo
Puede descargar la aplicación de ejemplo desde https://github.com/RickAndMSFT/Async-ASP.NET en el sitio de GitHub. El repositorio consta de tres proyectos:
- WebAppAsync: el proyecto de ASP.NET Web Forms que consume el servicio WebAPIpwg de la API web. La mayoría del código de este tutorial procede de este proyecto.
- WebAPIpgw: el proyecto de API web de ASP.NET MVC 4 que implementa los controladores
Products, Gizmos and Widgets
. Proporciona los datos del proyecto WebAppAsync y del proyectoMvc4Async. - Mvc4Async: el proyecto ASP.NET MVC 4 que contiene el código usado en otro tutorial. Realiza llamadas API web al servicio WebAPIpwg.
Página sincrónica de Gizmos
El código siguiente muestra el método sincrónicoPage_Load
que se usa para mostrar una lista de gizmos. (Para este artículo, un gizmo es un dispositivo mecánico ficticio).
public partial class Gizmos : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
var gizmoService = new GizmoService();
GizmoGridView.DataSource = gizmoService.GetGizmos();
GizmoGridView.DataBind();
}
}
El código siguiente muestra el métodoGetGizmos
del servicio gizmo.
public class GizmoService
{
public async Task<List<Gizmo>> GetGizmosAsync(
// Implementation removed.
public List<Gizmo> GetGizmos()
{
var uri = Util.getServiceUri("Gizmos");
using (WebClient webClient = new WebClient())
{
return JsonConvert.DeserializeObject<List<Gizmo>>(
webClient.DownloadString(uri)
);
}
}
}
El métodoGizmoService GetGizmos
pasa un URI a un servicio HTTP de API web de ASP.NET que devuelve una lista de datos de gizmos. El proyecto WebAPIpgw contiene la implementación de la API webgizmos, widget
y los controladoresproduct
.
En la imagen siguiente se muestra la página gizmos del proyecto de ejemplo.
Crear una página de Gizmos asincrónica
El ejemplo usa las nuevas palabras clave async y await (disponibles en .NET 4.5 y Visual Studio 2012) para permitir que el compilador sea responsable de mantener las transformaciones complicadas necesarias para la programación asincrónica. El compilador permite escribir código mediante las construcciones de flujo de control sincrónicas de C# y el compilador aplica automáticamente las transformaciones necesarias para usar devoluciones de llamada que evitan que los subprocesos estén ocupados.
Las páginas asincrónicas de ASP.NET deben incluir la directiva Page con el atributo Async
establecido en "true". El código siguiente muestra la directiva Page con el atributoAsync
establecido en "true" para la página de GizmosAsync.aspx.
<%@ Page Async="true" Language="C#" AutoEventWireup="true"
CodeBehind="GizmosAsync.aspx.cs" Inherits="WebAppAsync.GizmosAsync" %>
El código siguiente muestra el método Gizmos
sincrónico Page_Load
y la página asincrónicaGizmosAsync
. Si el explorador admite HTML 5< para marcar > elementos, verá los cambios en resaltado amarilloGizmosAsync
.
protected void Page_Load(object sender, EventArgs e)
{
var gizmoService = new GizmoService();
GizmoGridView.DataSource = gizmoService.GetGizmos();
GizmoGridView.DataBind();
}
Versión asincrónica:
protected void Page_Load(object sender, EventArgs e)
{
RegisterAsyncTask(new PageAsyncTask(GetGizmosSvcAsync));
}
private async Task GetGizmosSvcAsync()
{
var gizmoService = new GizmoService();
GizmosGridView.DataSource = await gizmoService.GetGizmosAsync();
GizmosGridView.DataBind();
}
Se aplicaron los siguientes cambios para permitir que la páginaGizmosAsync
sea asincrónica.
- La directiva Page debe tener el atributo
Async
establecido en "true". - El método
RegisterAsyncTask
se usa para registrar una tarea asincrónica que contiene el código que se ejecuta de forma asincrónica. - El nuevo método
GetGizmosSvcAsync
se marca con la palabra clave async, que indica al compilador que genere devoluciones de llamada para partes del cuerpo y que cree automáticamente unTask
que se devuelve. - "Async" se anexó al nombre del método asincrónico. No es necesario anexar "Async", pero es la convención al escribir métodos asincrónicos.
- El tipo de valor devuelto del nuevo método
GetGizmosSvcAsync
esTask
. El tipo de valor devuelto deTask
representa el trabajo en curso y proporciona a los llamadores del método un manipulador a través del cual esperar a que finalice la operación asincrónica. - La palabra clave await se aplicó a la llamada de servicio web.
- Se llamó a la API de servicio web asincrónica (
GetGizmosAsync
).
Dentro del cuerpo del método GetGizmosSvcAsync
se llama a otro método asincrónico GetGizmosAsync
. GetGizmosAsync
devuelve inmediatamente un Task<List<Gizmo>>
que finalizará eventualmente cuando los datos estén disponibles. Dado que no desea hacer nada más hasta que tenga los datos de gizmo, el código espera la tarea (mediante la palabra clave await). Puede usar la palabra clave await solo en los métodos anotados con la palabra clave async.
La palabra clave await no ocupa el subproceso hasta que se complete la tarea. Registra el resto del método como devolución de llamada en la tarea y devuelve inmediatamente. Cuando la tarea esperada esté completa, invocará esa devolución de llamada y, por tanto, reanudará la ejecución del método justo donde se dejó. Para obtener más información sobre el uso de las palabras clave await y async y el espacio de nombres Tarea, vea las referencias asincrónicas.
El siguiente código muestra los métodos GetGizmos
y GetGizmosAsync
.
public List<Gizmo> GetGizmos()
{
var uri = Util.getServiceUri("Gizmos");
using (WebClient webClient = new WebClient())
{
return JsonConvert.DeserializeObject<List<Gizmo>>(
webClient.DownloadString(uri)
);
}
}
public async Task<List<Gizmo>> GetGizmosAsync()
{
var uri = Util.getServiceUri("Gizmos");
using (WebClient webClient = new WebClient())
{
return JsonConvert.DeserializeObject<List<Gizmo>>(
await webClient.DownloadStringTaskAsync(uri)
);
}
}
Los cambios asincrónicos son similares a los realizados anteriormente en GizmosAsync.
- La firma del método se anotó con la palabra clave async, el tipo de valor devuelto se cambió a
Task<List<Gizmo>>
y Async se anexó al nombre del método. - La clase HttpClient asincrónica se usa en lugar de la clase WebClient sincrónica.
- La palabra clave await se aplicó al método asincrónico HttpClientGetAsync.
En la imagen siguiente se muestra la vista asincrónica de gizmo.
La presentación en los exploradores de los datos de gizmos es idéntica a la vista creada por la llamada sincrónica. La única diferencia es que la versión asincrónica puede ser más eficaz en cargas pesadas.
Notas de RegisterAsyncTask
Los métodos conectados con RegisterAsyncTask
se ejecutarán inmediatamente después de PreRender.
Si usa eventos de página async void directamente, como se muestra en el código siguiente:
protected async void Page_Load(object sender, EventArgs e) {
await ...;
// do work
}
ya no tiene control total sobre cuándo se ejecutan los eventos. Por ejemplo, si un .aspx y un .Master definen eventos Page_Load
y uno o ambos son asincrónicos, no se puede garantizar el orden de ejecución. Se aplica el mismo orden indeterminado para los controladores de eventos (como async void Button_Click
).
Realizar varias operaciones en paralelo
Los métodos asincrónicos tienen una ventaja significativa sobre los métodos sincrónicos cuando una acción debe realizar varias operaciones independientes. En el ejemplo proporcionado, la página sincrónica PWG.aspx(para Productos, Widgets y Gizmos) muestra los resultados de tres llamadas de servicio web para obtener una lista de productos, widgets y gizmos. El proyecto de API web de ASP.NET que proporciona estos servicios usa Task.Delay para simular la latencia o llamadas de red lentas. Cuando el retraso se establece en 500 milisegundos, la página asincrónica PWGasync.aspx tarda un poco más de 500 milisegundos en completarse mientras que la versión sincrónica PWG
toma más de 1500 milisegundos. La página sincrónica PWG.aspx se muestra en el código siguiente.
protected void Page_Load(object sender, EventArgs e)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
var widgetService = new WidgetService();
var prodService = new ProductService();
var gizmoService = new GizmoService();
var pwgVM = new ProdGizWidgetVM(
widgetService.GetWidgets(),
prodService.GetProducts(),
gizmoService.GetGizmos()
);
WidgetGridView.DataSource = pwgVM.widgetList;
WidgetGridView.DataBind();
ProductGridView.DataSource = pwgVM.prodList;
ProductGridView.DataBind();
GizmoGridView.DataSource = pwgVM.gizmoList;
GizmoGridView.DataBind();
stopWatch.Stop();
ElapsedTimeLabel.Text = String.Format("Elapsed time: {0}",
stopWatch.Elapsed.Milliseconds / 1000.0);
}
A continuación se muestra el código asincrónico PWGasync
subyacente.
protected void Page_Load(object sender, EventArgs e)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
RegisterAsyncTask(new PageAsyncTask(GetPWGsrvAsync));
stopWatch.Stop();
ElapsedTimeLabel.Text = String.Format("Elapsed time: {0}",
stopWatch.Elapsed.Milliseconds / 1000.0);
}
private async Task GetPWGsrvAsync()
{
var widgetService = new WidgetService();
var prodService = new ProductService();
var gizmoService = new GizmoService();
var widgetTask = widgetService.GetWidgetsAsync();
var prodTask = prodService.GetProductsAsync();
var gizmoTask = gizmoService.GetGizmosAsync();
await Task.WhenAll(widgetTask, prodTask, gizmoTask);
var pwgVM = new ProdGizWidgetVM(
widgetTask.Result,
prodTask.Result,
gizmoTask.Result
);
WidgetGridView.DataSource = pwgVM.widgetList;
WidgetGridView.DataBind();
ProductGridView.DataSource = pwgVM.prodList;
ProductGridView.DataBind();
GizmoGridView.DataSource = pwgVM.gizmoList;
GizmoGridView.DataBind();
}
En la imagen siguiente se muestra la vista devuelta desde la página asincrónica PWGasync.aspx.
Usar un token de cancelación
Los métodos asincrónicos que devuelven Task
son cancelables, es decir, toman un parámetro CancellationToken cuando se proporciona uno con el atributo AsyncTimeout
de la directiva Page. En el código siguiente se muestra la página GizmosCancelAsync.aspx con un tiempo de espera de un segundo.
<%@ Page Async="true" AsyncTimeout="1"
Language="C#" AutoEventWireup="true"
CodeBehind="GizmosCancelAsync.aspx.cs"
Inherits="WebAppAsync.GizmosCancelAsync" %>
El código siguiente muestra el archivo GizmosCancelAsync.aspx.cs.
protected void Page_Load(object sender, EventArgs e)
{
RegisterAsyncTask(new PageAsyncTask(GetGizmosSvcCancelAsync));
}
private async Task GetGizmosSvcCancelAsync(CancellationToken cancellationToken)
{
var gizmoService = new GizmoService();
var gizmoList = await gizmoService.GetGizmosAsync(cancellationToken);
GizmosGridView.DataSource = gizmoList;
GizmosGridView.DataBind();
}
private void Page_Error(object sender, EventArgs e)
{
Exception exc = Server.GetLastError();
if (exc is TimeoutException)
{
// Pass the error on to the Timeout Error page
Server.Transfer("TimeoutErrorPage.aspx", true);
}
}
En la aplicación de ejemplo proporcionada, al seleccionar el vínculo GizmosCancelAsync se llama a la página GizmosCancelAsync.aspx y se muestra la cancelación (por tiempo de espera) de la llamada asincrónica. Dado que el tiempo de retraso está dentro de un intervalo aleatorio, es posible que tenga que actualizar la página un par de veces para obtener el mensaje de error de tiempo de espera agotado.
Configuración del servidor para llamadas de servicio web de alta simultaneidad o latencia
Para apreciar los beneficios de una aplicación web asincrónica, es posible que tenga que realizar algunos cambios en la configuración predeterminada del servidor. Tenga en cuenta lo siguiente al configurar y realizar la prueba de esfuerzo de la aplicación web asincrónica.
Windows 7, Windows Vista, Window 8 y todos los sistemas operativos cliente de Windows tienen un máximo de 10 solicitudes simultáneas. Necesitará un sistema operativo Windows Server para ver los beneficios de los métodos asincrónicos con una carga alta.
Registre .NET 4.5 con IIS desde un símbolo del sistema con privilegios elevados mediante el siguiente comando:
%windir%\Microsoft.NET\Framework64 \v4.0.30319\aspnet_regiis -i
Ver Herramienta de registro de IIS en ASP.NET (Aspnet_regiis.exe)Es posible que tenga que aumentar el límite de cola de HTTP.sys del valor predeterminado de 1 000 a 5 000. Si la configuración es demasiado baja, es posible que vea a HTTP.sys rechazar solicitudes con un estado HTTP 503. Para cambiar el límite de cola de HTTP.sys:
- Abra el administrador de IIS y vaya al panel grupos de aplicaciones.
- Haga clic con el botón derecho en el grupo de aplicaciones de destino y seleccione Configuración avanzada.
- En el cuadro de diálogo Configuración avanzada, cambie Longitud de cola de 1 000 a 5 000.
Tenga en cuenta en las imágenes anteriores, .NET Framework aparece como v4.0, aunque el grupo de aplicaciones use .NET 4.5. Para comprender esta discrepancia, consulte lo siguiente:
Cómo establecer una aplicación de IIS o AppPool para usar ASP.NET 3.5 en lugar de 2.0
Si la aplicación usa servicios web o System.NET para comunicarse con un back-end a través de HTTP, es posible que tenga que aumentar el elemento connectionManagement/maxconnection. En el caso de las aplicaciones ASP.NET, esta característica está limitada por la característica autoConfig a 12 veces la cantidad de CPU. Esto significa que, en un procesador cuádruple, puede tener como máximo 12 * 4 = 48 conexiones simultáneas a un punto de conexión IP. Dado que esto está vinculado a autoConfig, la manera más fácil de aumentar
maxconnection
en una aplicación de ASP.NET es establecer System.Net.ServicePointManager.DefaultConnectionLimitmediante programación en la forma del métodoApplication_Start
del archivo global.asax. Consulte la descarga de muestra para obtener un ejemplo.En .NET 4.5, el valor predeterminado de 5 000 para MaxConcurrentRequestsPerCPU debería ser correcto.