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.
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. En este tutorial se incluyen las secciones siguientes.
- Cómo el grupo de subprocesos procesa las solicitudes
- Elección de 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
- Realización de varias operaciones en paralelo
- Uso de un token de cancelación
- Configuración del servidor para llamadas de servicio web de alta simultaneidad o latencia alta
Se proporciona un ejemplo completo para este tutorial en
https://github.com/RickAndMSFT/Async-ASP.NET/ en el sitio GitHub.
ASP.NET 4.5 Web Pages en combinación .NET 4.5 permite registrar métodos asincrónicos que devuelven un objeto de tipo Task. .NET Framework 4 introdujo un concepto de programación asincrónico denominado Task y ASP.NET 4.5 admite Task. 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 se basa en esta compatibilidad asincrónica con las palabras clave await y async que hacen que el trabajo con objetos Task sea mucho menos complejo que los enfoques asincrónicos anteriores. La palabra clave await es abreviada 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 asincrónica representa una sugerencia que puede usar para marcar métodos como métodos asincrónicos basados en tareas. La combinación de await, async y el objeto Task 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 (TAP). En este tutorial se supone que está familiarizado con la programación asincrónica mediante el uso de las palabras clave await y async y el espacio de nombres Task.
Para obtener más información sobre el uso de palabras clave await y async y el espacio de nombres Task , vea las siguientes referencias.
- Documento técnico: Asincronía en .NET
- Preguntas más frecuentes sobre Async/Await
- Programación asincrónica de Visual Studio
Cómo el grupo de subprocesos procesa las solicitudes
En el servidor web, .NET Framework mantiene un grupo de subprocesos que se usan para atender solicitudes ASP.NET. Cuando llega una solicitud, se envía un subproceso del grupo para procesar esa solicitud. Si la solicitud se procesa de forma sincrónica, el subproceso que procesa la solicitud está ocupado mientras se procesa la solicitud y ese subproceso no puede atender otra solicitud.
Esto tal vez no sea un problema, ya que el grupo de subprocesos se puede hacer lo suficientemente grande como para soportar muchos 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 5000). En aplicaciones grandes con alta simultaneidad de solicitudes de larga duración, es posible que todos los subprocesos disponibles estén ocupados. Esta condición se conoce como inaniación de hilos. Cuando se alcanza esta condición, el servidor web pone en cola las solicitudes. Si la cola de solicitudes se llena, el servidor web rechaza las solicitudes con un estado HTTP 503 (servidor demasiado ocupado). El grupo de subprocesos CLR tiene limitaciones en la incorporación de nuevos subprocesos. Si la concurrencia es repentina (es decir, si su 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 alta latencia, la tasa limitada de inyección de subprocesos puede hacer que la aplicación tenga un rendimiento muy deficiente. 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 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 se realiza un trabajo asincrónico, no siempre se utiliza un hilo. Por ejemplo, al realizar una solicitud de servicio web asincrónica, ASP.NET no usará ningún subproceso entre la llamada al método asincrónicoy 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.
Procesamiento de 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 como una solicitud sincrónica. Por ejemplo, si una solicitud realiza una llamada de servicio web que requiere dos segundos para completarse, la solicitud tarda dos segundos si se realiza de forma sincrónica o asincrónica. Sin embargo, durante una llamada asincrónica, no se impide que un subproceso responda a otras solicitudes mientras espera a que se complete la primera solicitud. Por lo tanto, las solicitudes asincrónicas evitan que se formen colas de solicitudes y el crecimiento del grupo de subprocesos cuando hay muchas solicitudes simultáneas que invocan operaciones de larga duración.
Elección de métodos sincrónicos o asincrónicos
En esta sección se enumeran las directrices para cuándo usar métodos sincrónicos o asincrónicos. Estas son simplemente directrices; examine cada aplicación individualmente para determinar si los métodos asincrónicos ayudan con el rendimiento.
En general, use métodos sincrónicos para las condiciones siguientes:
- Las operaciones son simples o de ejecución corta.
- La simplicidad es más importante que la eficacia.
- Las operaciones son principalmente operaciones de CPU en lugar de operaciones que implican una sobrecarga extensa de disco o red. El uso de métodos asincrónicos en operaciones enlazadas a CPU no proporciona ventajas y genera más sobrecarga.
En general, use métodos asincrónicos para las condiciones siguientes:
Está llamando a servicios que se pueden consumir mediante métodos asincrónicos y utiliza .NET 4.5 o versiones superiores.
Las operaciones están enlazadas a la red o enlazadas a E/S en lugar de enlazadas a cpu.
El paralelismo es más importante que la simplicidad del código.
Quiere proporcionar un mecanismo que permita a los usuarios cancelar una solicitud de larga duración.
Cuando el beneficio de cambiar hilos supera el costo del cambio de contexto. En general, debe convertir un método en asincrónico si el método sincrónico bloquea el subproceso de solicitud de ASP.NET sin realizar ningún trabajo. Al realizar la llamada de manera asincrónica, el subproceso de solicitud de ASP.NET no se queda bloqueado sin hacer nada mientras espera a que se complete la solicitud del servicio web.
Las pruebas muestran que las operaciones de bloqueo son un cuello de botella en el rendimiento del sitio y que IIS puede atender más solicitudes mediante métodos asincrónicos para estas llamadas de bloqueo.
En el ejemplo descargable se muestra cómo usar métodos asincrónicos de forma eficaz. 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 los métodos del API web de ASP.NET, que a su vez llaman a Task.Delay para simular llamadas de servicios web prolongadas. La mayoría de las aplicaciones de producción no mostrarán ventajas tan obvias para usar métodos asincrónicos.
Pocas aplicaciones requieren que todos los métodos sean asincrónicos. A menudo, convertir algunos métodos sincrónicos en métodos asincrónicos proporciona el mejor aumento de eficiencia para la cantidad de trabajo necesario.
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 web Forms ASP.NET 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
Products, Gizmos and Widgetscontroladores. Proporciona los datos del proyecto WebAppAsync y del proyecto Mvc4Async . - 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 Page_Load método sincrónico 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 GetGizmos método 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 GizmoService GetGizmos método 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 web gizmos, widget y los controladores product.
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 asincrónicas 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 estructuras de control de flujo sincrónico de C# y el compilador aplica automáticamente las transformaciones necesarias para usar devoluciones de llamada y evitar el bloqueo de subprocesos.
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 Async atributo 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 Gizmos método sincrónico Page_Load y la GizmosAsync página asincrónica. Si tu navegador admite el elemento
protected void Page_Load(object sender, EventArgs e)
{
var gizmoService = new GizmoService();
GizmoGridView.DataSource = gizmoService.GetGizmos();
GizmoGridView.DataBind();
}
La 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 GizmosAsync página sea asincrónica.
- La directiva Page debe tener el
Asyncatributo establecido en "true". - El
RegisterAsyncTaskmétodo se usa para registrar una tarea asincrónica que contiene el código que se ejecuta de forma asincrónica. - El nuevo
GetGizmosSvcAsyncmétodo se marca con la palabra clave async, que indica al compilador que genere callbacks para partes del código y que cree automáticamente unTaskque 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
GetGizmosSvcAsyncmétodo esTask. El tipo de valor devuelto deTaskrepresenta el trabajo en curso y proporciona a los invocadores del método un identificador mediante el cual esperar la finalización de 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 GetGizmosSvcAsync método se llama a otro método GetGizmosAsync asincrónico.
GetGizmosAsync devuelve inmediatamente un Task<List<Gizmo>> que finalizará finalmente cuando los datos estén disponibles. Dado que no desea hacer nada más hasta que tenga los datos del dispositivo, el código suspende la tarea mediante la palabra clave await. Puede usar la palabra clave await solo en métodos anotados con la palabra clave async.
La palabra clave await no bloquea el subproceso hasta que se complete la tarea. Registra el resto del método como callback en la tarea y se devuelve inmediatamente. Cuando eventualmente finalice la tarea esperada, invocará esa devolución de llamada y, por lo 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 Task , 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 siguiente imagen se muestra la vista asincrónica del dispositivo.
La presentación de 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 directamente eventos de página void asincrónicos, 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 tanto un .aspx como 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 ).
Realización de 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 de PWGasync.aspx asincrónica 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 PWG.aspx sincrónica 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.
Uso de un token de cancelación
Los métodos asincrónicos que devuelven Taskson cancelables, es decir, toman un parámetro CancellationToken cuando se proporciona uno con el AsyncTimeout atributo de la directiva Page . El siguiente código 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 muestra proporcionada, al seleccionar el vínculo GizmosCancelAsync, se accede a la página GizmosCancelAsync.aspx, donde se demuestra la cancelación de la llamada asincrónica debido a un tiempo de espera. 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.
Configuración del servidor para llamadas de servicio web de alta simultaneidad o latencia alta
Para obtener las ventajas 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 pruebas de resistencia en su 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 las ventajas de los métodos asincrónicos con una carga alta.
Registre .NET 4.5 con IIS desde una ventana de comandos con privilegios elevados mediante el siguiente comando:
%windir%\Microsoft.NET\Framework64 \v4.0.30319\aspnet_regiis -i
Consulte ASP.NET herramienta de registro de IIS (Aspnet_regiis.exe)Es posible que tenga que aumentar el límite de la cola de HTTP.sys del valor predeterminado de 1.000 a 5.000. Si la configuración es demasiado baja, es posible que vea 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 1000 a 5000.
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:
Control de versiones de .NET y multi-targeting: .NET 4.5 es una actualización local a .NET 4.0
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 el número de CPU. Esto significa que, en un sistema de cuatro procesadores, puede tener hasta 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
maxconnectionen una aplicación de ASP.NET es establecer System.Net.ServicePointManager.DefaultConnectionLimit mediante programación en el método fromApplication_Starten el archivo global.asax . Vea el archivo de descarga como ejemplo.En .NET 4.5, el valor predeterminado de 5000 para MaxConcurrentRequestsPerCPU debe ser correcto.