name method documentacion comentarios c# asp.net asynchronous async-ctp

method - param name c#



¿Por qué usar solicitudes asincrónicas en lugar de usar un threadpool más grande? (3)

  1. Async / await no está basado en hilos; se basa en el procesamiento asincrónico. Cuando realiza una espera asincrónica en ASP.NET, el hilo de solicitud se devuelve al grupo de subprocesos, por lo que no hay subprocesos que atiendan esa solicitud hasta que finalice la operación asincrónica. Como la sobrecarga de las solicitudes es menor que la sobrecarga de las hebras, esto significa que async / await puede escalar mejor que el grupo de subprocesos.
  2. La solicitud tiene un recuento de operaciones asincrónicas sobresalientes. Este conteo es administrado por la implementación de ASP.NET de SynchronizationContext . Puede leer más sobre SynchronizationContext en mi artículo de MSDN : cubre cómo funciona el SynchronizationContext de ASP.NET y cómo await SynchronizationContext .

El procesamiento asincrónico ASP.NET era posible antes de async / await: podía usar páginas asíncronas y usar componentes EAP como WebClient (la programación asincrónica basada en eventos es un estilo de programación asincrónica basada en SynchronizationContext ). Async / await también usa SynchronizationContext , pero tiene una sintaxis mucho más fácil.

Durante Techdays aquí en los Países Bajos, Steve Sanderson realizó una presentación sobre C # 5, ASP.NET MVC 4 y Web asíncrona .

Explicó que cuando las solicitudes tardan mucho en finalizar, todos los hilos del threadpool se vuelven ocupados y las nuevas solicitudes tienen que esperar. El servidor no puede manejar la carga y todo se ralentiza.

A continuación, mostró cómo el uso de webrequests asincrónicos mejora el rendimiento porque el trabajo se delega a otro hilo y el subproceso puede responder rápidamente a las nuevas solicitudes entrantes. Incluso demostró esto y mostró que 50 solicitudes concurrentes primero tomaron 50 * 1s pero con el comportamiento asincrónico en su lugar solo 1,2 s en total.

Pero después de ver esto, todavía tengo algunas preguntas.

  1. ¿Por qué no podemos simplemente usar un threadpool más grande? ¿No está usando async / await para mostrar otro subproceso más lento y luego simplemente aumentar el threadpool desde el principio? ¿No es que el servidor en el que corremos repentinamente tiene más hilos o algo así?

  2. La solicitud del usuario aún está esperando que finalice el subproceso asincrónico. Si el hilo del grupo está haciendo otra cosa, ¿cómo se mantiene ocupado el hilo ''UI''? Steve mencionó algo sobre ''un kernel inteligente que sabe cuándo algo está terminado''. ¿Como funciona esto?


Esta es una muy buena pregunta, y entenderla es clave para entender por qué el IO asíncrono es tan importante. La razón por la cual la nueva característica async / await se ha agregado a C # 5.0 es simplificar la escritura de código asíncrono. El soporte para el procesamiento asincrónico en el servidor no es nuevo, sin embargo, existe desde ASP.NET 2.0.

Como Steve le mostró, con el procesamiento sincrónico, cada solicitud en ASP.NET (y WCF) toma un hilo del grupo de subprocesos. El problema que demostró es un problema conocido llamado " inanición de grupo de subprocesos ". Si realiza IO sincrónico en su servidor, el hilo de la agrupación de hilos permanecerá bloqueado (sin hacer nada) mientras dure el IO. Dado que hay un límite en el número de subprocesos en el grupo de subprocesos, bajo carga, esto puede generar una situación en la que todos los subprocesos de subprocesos se bloquean esperando E / S, y las solicitudes comienzan a ponerse en cola, lo que provoca un aumento en el tiempo de respuesta. Dado que todos los hilos están esperando a que se complete un IO, verá una ocupación de CPU cercana al 0% (aunque los tiempos de respuesta van por el techo).

Lo que estás preguntando ( ¿por qué no podemos simplemente usar un threadpool más grande? ) Es una muy buena pregunta. De hecho, así es como la mayoría de la gente ha estado resolviendo el problema del hambruna en el pool de subprocesos hasta ahora: solo tiene más hilos en el grupo de subprocesos. Algunos documentos de Microsoft incluso lo indican como una solución para situaciones en las que puede producirse la falta de espacio en la fila de subprocesos. Esta es una solución aceptable, y hasta C # 5.0, era mucho más fácil hacer eso, que reescribir el código para que fuera completamente asincrónico.

Sin embargo, hay algunos problemas con el enfoque:

  • No hay ningún valor que funcione en todas las situaciones : la cantidad de subprocesos de grupo de subprocesos que necesitará dependerá linealmente de la duración del IO y de la carga en su servidor. Desafortunadamente, la latencia de IO es en su mayoría impredecible. Aquí hay un ejemplo: supongamos que realiza solicitudes HTTP a un servicio web de terceros en su aplicación ASP.NET, que tarda aproximadamente 2 segundos en completarse. Se encuentra con la falta de espacio en la fila de subprocesos, por lo que decide aumentar el tamaño del grupo de subprocesos a, digamos, 200 subprocesos, y luego vuelve a funcionar correctamente. El problema es que tal vez la próxima semana, el servicio web tendrá problemas técnicos que aumentan su tiempo de respuesta a 10 segundos. De repente, la falta de espacio en el hilo ha vuelto, porque los hilos están bloqueados 5 veces más, por lo que ahora necesitas aumentar el número 5 veces, hasta 1.000 hilos.

  • Escalabilidad y rendimiento : el segundo problema es que, si lo hace, seguirá utilizando un hilo por solicitud. Los hilos son un recurso costoso. Cada subproceso administrado en .NET requiere una asignación de memoria de 1 MB para la pila. Para una página web que hace IO durante los últimos 5 segundos, y con una carga de 500 solicitudes por segundo, necesitará 2.500 subprocesos en su grupo de subprocesos, lo que significa 2,5 GB de memoria para las pilas de subprocesos que se quedarán sin hacer nada. Luego tiene el problema del cambio de contexto, que tendrá un alto costo en el rendimiento de su máquina (afectando a todos los servicios en la máquina, no solo a su aplicación web). A pesar de que Windows hace un buen trabajo al ignorar los hilos en espera, no está diseñado para manejar una gran cantidad de subprocesos. Recuerde que la mayor eficiencia se obtiene cuando el número de subprocesos en ejecución es igual al número de CPU lógicas en la máquina (por lo general, no más de 16).

Así que aumentar el tamaño del grupo de subprocesos es una solución, y la gente lo ha estado haciendo durante una década (incluso en los propios productos de Microsoft), es menos escalable y eficiente, en términos de uso de memoria y CPU, y siempre estás en la misericordia de un aumento repentino de la latencia de IO que causaría la inanición. Hasta C # 5.0, la complejidad del código asincrónico no valía la pena para muchas personas. async / await cambia todo como ahora, puede beneficiarse de la escalabilidad de I / O asíncrona y escribir código simple al mismo tiempo.

Más detalles: http://msdn.microsoft.com/en-us/library/ff647787.aspx " Use llamadas asíncronas para invocar servicios web u objetos remotos cuando existe la oportunidad de realizar un procesamiento paralelo adicional mientras se realiza la llamada al servicio web. Siempre que sea posible, evite las llamadas síncronas (de bloqueo) a los servicios web porque las llamadas al servicio web saliente se realizan mediante el uso de subprocesos del grupo de subprocesos de ASP.NET. El bloqueo de llamadas reduce el número de subprocesos disponibles para procesar otras solicitudes entrantes.


Imagine el grupo de hilos como un conjunto de trabajadores que ha empleado para hacer su trabajo. Sus trabajadores ejecutan instrucciones rápidas de CPU para su código.

Ahora su trabajo depende del trabajo de otro tipo lento; el chico lento es el disco o la red . Por ejemplo, su trabajo puede tener dos partes, una parte que debe ejecutarse antes del trabajo del tipo lento, y una parte que debe ejecutarse después del trabajo del tipo lento.

¿Cómo recomendarías a tus trabajadores que hagan tu trabajo? ¿Le dirías a cada trabajador: "Haz esta primera parte, luego espera a que termine ese hombre lento, y luego haz tu segunda parte"? ¿Aumentarías el número de tus trabajadores porque todos parecen estar esperando a ese tipo lento y no eres capaz de satisfacer a los nuevos clientes? ¡No!

En cambio, le pedirá a cada trabajador que haga la primera parte y le pedirá al hombre lento que regrese y deje caer un mensaje en una cola cuando haya terminado. Le diría a cada trabajador (o tal vez a un subconjunto dedicado de trabajadores) que busque los mensajes realizados en la cola y que haga la segunda parte del trabajo.

El kernel inteligente al que hace referencia anteriormente es la capacidad del sistema operativo para mantener dicha cola para los mensajes de finalización de IO de disco y red lenta.