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.
Este tutorial le enseñará los conceptos básicos de la creación de una aplicación web asincrónica ASP.NET MVC mediante Visual Studio Express 2012 para Web, que es una versión gratuita de Microsoft Visual Studio. También puede usar Visual Studio 2012.
Se proporciona un ejemplo completo para este tutorial en github https://github.com/RickAndMSFT/Async-ASP.NET/
La ASP.NET clase Controller de MVC 4 en combinación .NET 4.5 permite escribir métodos de acción asincrónicos que devuelven un objeto de tipo Task<ActionResult>. .NET Framework 4 introdujo un concepto de programación asincrónica denominado Task y ASP.NET MVC 4 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 una aplicación web que experimenta un gran número de solicitudes concurrentes al inicio o tiene una carga de ráfaga (donde la concurrencia aumenta repentinamente), convertir las llamadas de servicio web en asincrónicas incrementa la capacidad de respuesta de la aplicación. Una solicitud asincrónica tarda el mismo tiempo en procesarse como una solicitud sincrónica. 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 de acción sincrónicos o asincrónicos
En esta sección se enumeran las directrices para cuándo usar métodos de acción 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 de acción 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, debería convertir un método en asincrónico si el método sincrónico espera en el hilo de solicitud de ASP.NET sin realizar ningún trabajo. Al hacer la llamada asincrónica, el hilo de solicitud de ASP.NET no queda inactivo mientras espera la finalización de 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 de acción 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 MVC 4 mediante .NET 4.5. El ejemplo no está pensado para ser una arquitectura de referencia para la programación asincrónica en ASP.NET MVC. 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 al usar métodos de acción asincrónicos.
Pocas aplicaciones requieren que todos los métodos de acción sean asincrónicos. A menudo, convertir algunos métodos de acción 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:
- Mvc4Async: el proyecto ASP.NET MVC 4 que contiene el código usado en este tutorial. Realiza llamadas api web al servicio WebAPIpgw .
-
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 . - WebAppAsync: el proyecto de ASP.NET Web Forms que se usa en otro tutorial.
El método de acción sincrónica de Gizmos
El código siguiente muestra el Gizmos método de acción sincrónica que se usa para mostrar una lista de gizmos. (Para este artículo, un gizmo es un dispositivo mecánico ficticio).
public ActionResult Gizmos()
{
ViewBag.SyncOrAsync = "Synchronous";
var gizmoService = new GizmoService();
return View("Gizmos", gizmoService.GetGizmos());
}
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 "vista gizmos" del proyecto de ejemplo.
Crear un método de acción asincrónico de Gizmos
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.
El código siguiente muestra el Gizmos método sincrónico y el GizmosAsync método asincrónico. Si su navegador admite el elemento HTML 5<mark>, verá los cambios en GizmosAsync destacados en amarillo.
public ActionResult Gizmos()
{
ViewBag.SyncOrAsync = "Synchronous";
var gizmoService = new GizmoService();
return View("Gizmos", gizmoService.GetGizmos());
}
public async Task<ActionResult> GizmosAsync()
{
ViewBag.SyncOrAsync = "Asynchronous";
var gizmoService = new GizmoService();
return View("Gizmos", await gizmoService.GetGizmosAsync());
}
Se aplicaron los siguientes cambios para permitir que GizmosAsync sea asincrónico.
- El método se marca con la palabra clave async, que indica al compilador que genere devoluciones de llamada para partes del cuerpo del método y que cree automáticamente un
Task<ActionResult>que se devuelve. - "Async" se anexó al nombre del método. No es necesario anexar "Async", pero es la convención al escribir métodos asincrónicos.
- El tipo de valor devuelto se cambió de
ActionResultaTask<ActionResult>. El tipo de valor devuelto deTask<ActionResult>representa 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. En este caso, el autor de la llamada es el servicio web.Task<ActionResult>representa el trabajo continuo con un resultado deActionResult. - 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 GetGizmosAsync 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 (HttpClient httpClient = new HttpClient())
{
var response = await httpClient.GetAsync(uri);
return (await response.Content.ReadAsAsync<List<Gizmo>>());
}
}
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 .
- La palabra clave await se aplicó a los métodos asincrónicos HttpClient .
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.
Realización de varias operaciones en paralelo
Los métodos de acción 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, el método PWGsincrónico (para Products, 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, el método asincrónico PWGasync tarda un poco más de 500 milisegundos en completarse mientras la versión sincrónica PWG toma más de 1500 milisegundos. El método sincrónico PWG se muestra en el código siguiente.
public ActionResult PWG()
{
ViewBag.SyncType = "Synchronous";
var widgetService = new WidgetService();
var prodService = new ProductService();
var gizmoService = new GizmoService();
var pwgVM = new ProdGizWidgetVM(
widgetService.GetWidgets(),
prodService.GetProducts(),
gizmoService.GetGizmos()
);
return View("PWG", pwgVM);
}
El método asincrónico PWGasync se muestra en el código siguiente.
public async Task<ActionResult> PWGasync()
{
ViewBag.SyncType = "Asynchronous";
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
);
return View("PWG", pwgVM);
}
En la imagen siguiente se muestra la vista devuelta desde el método PWGasync .
Uso de un token de cancelación
Los métodos de acción asincrónicos que devuelven Task<ActionResult>son cancelables, es decir, toman un parámetro CancellationToken cuando se proporciona uno con el atributo AsyncTimeout . El código siguiente muestra el GizmosCancelAsync método con un tiempo de espera de 150 milisegundos.
[AsyncTimeout(150)]
[HandleError(ExceptionType = typeof(TimeoutException),
View = "TimeoutError")]
public async Task<ActionResult> GizmosCancelAsync(
CancellationToken cancellationToken )
{
ViewBag.SyncOrAsync = "Asynchronous";
var gizmoService = new GizmoService();
return View("Gizmos",
await gizmoService.GetGizmosAsync(cancellationToken));
}
El código siguiente muestra la sobrecarga GetGizmosAsync, que toma un parámetro CancellationToken .
public async Task<List<Gizmo>> GetGizmosAsync(string uri,
CancellationToken cancelToken = default(CancellationToken))
{
using (HttpClient httpClient = new HttpClient())
{
var response = await httpClient.GetAsync(uri, cancelToken);
return (await response.Content.ReadAsAsync<List<Gizmo>>());
}
}
En la aplicación de ejemplo dada, al seleccionar el vínculo Demostración del token de cancelación se muestra la cancelación de la llamada asincrónica y se llama al método GizmosCancelAsync.
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 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 un símbolo del sistema con privilegios elevados:
%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:
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.