c# - tag - Realice operaciones asincrónicas en ASP.NET MVC con un hilo de ThreadPool en.NET 4
razor if (6)
Aquí hay un artículo excelente que recomendaría que leyeras para comprender mejor el procesamiento asíncrono en ASP.NET (que es lo que básicamente representan los controladores asíncronos).
Consideremos primero una acción síncrona estándar:
public ActionResult Index()
{
// some processing
return View();
}
Cuando se realiza una solicitud a esta acción, se extrae un subproceso del grupo de subprocesos y el cuerpo de esta acción se ejecuta en este subproceso. Entonces, si el procesamiento dentro de esta acción es lento, está bloqueando este hilo para todo el proceso, por lo que este hilo no puede reutilizarse para procesar otras solicitudes. Al final de la ejecución de la solicitud, el hilo se devuelve al grupo de subprocesos.
Ahora tomemos un ejemplo del patrón asincrónico:
public void IndexAsync()
{
// perform some processing
}
public ActionResult IndexCompleted(object result)
{
return View();
}
Cuando se envía una solicitud a la acción Índice, se extrae un hilo del grupo de subprocesos y se ejecuta el cuerpo del método IndexAsync
. Una vez que el cuerpo de este método termina de ejecutarse, el hilo se devuelve al grupo de subprocesos. Luego, utilizando AsyncManager.OutstandingOperations
estándar, una vez que AsyncManager.OutstandingOperations
la finalización de la operación asincrónica, se extrae otro subproceso del grupo de subprocesos y se ejecuta en él el cuerpo de la acción IndexCompleted
y el resultado se presenta al cliente.
Entonces, lo que podemos ver en este patrón es que una solicitud HTTP de un solo cliente podría ser ejecutada por dos hilos diferentes.
Ahora la parte interesante ocurre dentro del método IndexAsync
. Si tiene una operación de bloqueo dentro, está desperdiciando totalmente el propósito de los controladores asíncronos porque está bloqueando el hilo de trabajo (recuerde que el cuerpo de esta acción se ejecuta en un hilo extraído del grupo de subprocesos).
Entonces, ¿cuándo podemos aprovechar las ventajas de los controladores asíncronos?
En mi humilde opinión, podemos obtener más beneficios cuando tenemos operaciones intensivas de E / S (como las llamadas de bases de datos y de red a servicios remotos). Si tiene una operación intensiva de CPU, las acciones asincrónicas no le brindarán muchos beneficios.
Entonces, ¿por qué podemos obtener beneficios de las operaciones intensivas de E / S? Porque podríamos usar puertos de finalización de E / S. IOCP es extremadamente poderoso porque no consume ningún hilo o recurso en el servidor durante la ejecución de toda la operación.
¿Cómo trabajan?
Supongamos que queremos descargar el contenido de una página web remota utilizando el método WebClient.DownloadStringAsync . Llama a este método que registrará un IOCP dentro del sistema operativo y lo devolverá de inmediato. Durante el procesamiento de toda la solicitud, no se consumen hilos en su servidor. Todo sucede en el servidor remoto. Esto podría llevar mucho tiempo, pero no le importa, ya que no está poniendo en peligro sus hilos de trabajo. Una vez que se recibe una respuesta, se señala el IOCP, se extrae un subproceso del grupo de subprocesos y se ejecuta la devolución de llamada en este subproceso. Pero como puede ver, durante todo el proceso, no hemos monopolizado ningún hilo.
Lo mismo ocurre con métodos como FileStream.BeginRead, SqlCommand.BeginExecute, ...
¿Qué hay de paralelizar múltiples llamadas a la base de datos? Supongamos que tiene una acción de controlador síncrono en la que realizó 4 llamadas a la base de datos de bloqueo en secuencia. Es fácil calcular que si cada llamada a la base de datos tarda 200 ms, la acción de su controlador tardará aproximadamente 800ms en ejecutarse.
Si no necesita ejecutar esas llamadas secuencialmente, ¿la paralelización mejoraría el rendimiento?
Esa es la gran pregunta, que no es fácil de responder. Tal vez sí tal vez no. Dependerá completamente de cómo implemente esas llamadas a la base de datos. Si utiliza los controladores asíncronos y los puertos de finalización de E / S como se explicó anteriormente, aumentará el rendimiento de esta acción del controlador y de otras acciones, ya que no monopolizará los subprocesos de trabajo.
Por otro lado, si los implementa mal (con una llamada de base de datos de bloqueo realizada en un hilo del grupo de subprocesos), básicamente reducirá el tiempo total de ejecución de esta acción a aproximadamente 200 ms, pero habría consumido 4 subprocesos de trabajo para que podría haber degradado el rendimiento de otras solicitudes que podrían pasar hambre debido a la falta de subprocesos en el grupo para procesarlas.
Por lo tanto, es muy difícil y si no se siente listo para realizar pruebas exhaustivas en su aplicación, no implemente controladores asincrónicos, ya que es probable que haga más daño que beneficio. Impleméntelos solo si tiene una razón para hacerlo: por ejemplo, ha identificado que las acciones del controlador síncrono estándar son un cuello de botella para su aplicación (después de realizar extensas pruebas de carga y mediciones, por supuesto).
Ahora consideremos su ejemplo:
public ViewResult Index() {
Task.Factory.StartNew(() => {
//Do an advanced looging here which takes a while
});
return View();
}
Cuando se recibe una solicitud para la acción Index, se extrae un hilo del grupo de subprocesos para ejecutar su cuerpo, pero su cuerpo solo programa una nueva tarea usando TPL . Por lo tanto, la ejecución de la acción finaliza y el hilo se devuelve al grupo de subprocesos. Excepto que, TPL usa subprocesos del grupo de subprocesos para realizar su procesamiento. Por lo tanto, aunque el hilo original haya sido devuelto al grupo de hilos, habrá dibujado otro hilo de este grupo para ejecutar el cuerpo de la tarea. De modo que ha puesto en peligro 2 hilos de su valioso grupo.
Ahora consideremos lo siguiente:
public ViewResult Index() {
new Thread(() => {
//Do an advanced looging here which takes a while
}).Start();
return View();
}
En este caso, estamos generando manualmente un hilo. En este caso, la ejecución del cuerpo de la acción Index podría tomar un poco más de tiempo (porque generar un nuevo hilo es más costoso que dibujar uno de un grupo existente). Pero la ejecución de la operación de registro avanzada se realizará en un hilo que no es parte del conjunto. Por lo tanto, no estamos poniendo en peligro los hilos del grupo que permanecen libres para atender otras solicitudes.
Después de esta pregunta, me siento cómodo cuando uso operaciones asincrónicas en ASP.NET MVC. Entonces, escribí dos publicaciones de blog sobre eso:
Tengo demasiados malentendidos en mi mente sobre las operaciones asincrónicas en ASP.NET MVC.
Siempre escucho esta frase: la aplicación puede escalarse mejor si las operaciones se ejecutan de forma asíncrona
Y también escuché este tipo de oraciones: si tienes un gran volumen de tráfico, es mejor que no realices tus consultas de forma asíncrona: consumir 2 hilos adicionales para atender una solicitud quita recursos de otras solicitudes entrantes.
Creo que esas dos oraciones son inconsistentes.
No tengo mucha información sobre cómo funciona threadpool en ASP.NET, pero sé que threadpool tiene un tamaño limitado para los subprocesos. Entonces, la segunda oración tiene que estar relacionada con este tema.
Y me gustaría saber si las operaciones asincrónicas en ASP.NET MVC utilizan un hilo de ThreadPool en .NET 4?
Por ejemplo, cuando implementamos AsyncController, ¿cómo se estructura la aplicación? Si recibo mucho tráfico, ¿es una buena idea implementar AsyncController?
¿Hay alguien afuera que pueda quitarme esta cortina negra delante de mis ojos y explicarme el trato sobre la asincronía en ASP.NET MVC 3 (NET 4)?
Editar:
He leído este documento a continuación casi cientos de veces y entiendo el trato principal, pero todavía tengo confusión porque hay demasiados comentarios inconsistentes por ahí.
Uso de un controlador asincrónico en ASP.NET MVC
Editar:
Supongamos que tengo una acción de controlador como la siguiente (aunque no una implementación de AsyncController
):
public ViewResult Index() {
Task.Factory.StartNew(() => {
//Do an advanced looging here which takes a while
});
return View();
}
Como ven aquí, disparo una operación y me olvido de ella. Luego, regreso de inmediato sin esperar a que se complete.
En este caso, ¿esto tiene que usar un hilo de threadpool? Si es así, después de que se complete, ¿qué le sucede a ese hilo? ¿ GC
entra y limpia justo después de que se completa?
Editar:
Para la respuesta de @ Darin, aquí hay una muestra de código asíncrono que habla a la base de datos:
public class FooController : AsyncController {
//EF 4.2 DbContext instance
MyContext _context = new MyContext();
public void IndexAsync() {
AsyncManager.OutstandingOperations.Increment(3);
Task<IEnumerable<Foo>>.Factory.StartNew(() => {
return
_context.Foos;
}).ContinueWith(t => {
AsyncManager.Parameters["foos"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
Task<IEnumerable<Bars>>.Factory.StartNew(() => {
return
_context.Bars;
}).ContinueWith(t => {
AsyncManager.Parameters["bars"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
Task<IEnumerable<FooBar>>.Factory.StartNew(() => {
return
_context.FooBars;
}).ContinueWith(t => {
AsyncManager.Parameters["foobars"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
}
public ViewResult IndexCompleted(
IEnumerable<Foo> foos,
IEnumerable<Bar> bars,
IEnumerable<FooBar> foobars) {
//Do the regular stuff and return
}
}
Hay dos conceptos en juego aquí. En primer lugar, podemos hacer que nuestro código se ejecute en paralelo para ejecutarlo más rápido o programar código en otro hilo para evitar que el usuario espere. El ejemplo que tenías
public ViewResult Index() {
Task.Factory.StartNew(() => {
//Do an advanced looging here which takes a while
});
return View();
}
pertenece a la segunda categoría. El usuario obtendrá una respuesta más rápida, pero la carga de trabajo total en el servidor es mayor porque tiene que hacer el mismo trabajo + manejar el subproceso.
Otro ejemplo de esto sería:
public ViewResult Index() {
Task.Factory.StartNew(() => {
//Make async web request to twitter with WebClient.DownloadString()
});
Task.Factory.StartNew(() => {
//Make async web request to facebook with WebClient.DownloadString()
});
//wait for both to be ready and merge the results
return View();
}
Debido a que las solicitudes se ejecutan en paralelo, el usuario no tendrá que esperar, siempre y cuando lo hagan en serie. Pero deberías darte cuenta de que utilizamos más recursos aquí que si ejecutáramos en serie porque ejecutamos el código en muchos hilos mientras tenemos el hilo esperando también.
Esto está perfectamente bien en un escenario de cliente. Y es bastante común que para envolver código sincrónico de larga ejecución en una nueva tarea (ejecutarlo en otro subproceso) también mantenga la interfaz de usuario sensible o paralice para hacerlo más rápido. Sin embargo, un hilo todavía se usa para toda la duración. En un servidor con alta carga, esto podría ser contraproducente porque realmente usa más recursos. Esto es lo que la gente te advirtió sobre
Controladores asíncronos en MVC tienen otro objetivo sin embargo. El punto aquí es evitar tener subprocesos sin hacer nada (lo que puede perjudicar la escalabilidad). Realmente solo importa si las API que está llamando tienen métodos asíncronos. Como WebClient.DowloadStringAsync ().
El punto es que puedes dejar que se devuelva tu hilo para manejar las nuevas solicitudes hasta que la solicitud web finalice y te llame por devolución de llamada, que obtiene el mismo hilo o un nuevo y finaliza la solicitud.
Espero que entiendas la diferencia entre asincrónico y paralelo. Piense en el código paralelo como el código donde se encuentra el hilo y espere el resultado. Mientras que el código asíncrono es un código donde se le notificará cuando el código esté listo y pueda volver a trabajar en él, mientras tanto, el hilo puede hacer otro trabajo.
Las aplicaciones pueden escalar mejor si las operaciones se ejecutan de forma asíncrona, pero solo si hay recursos disponibles para dar servicio a las operaciones adicionales .
Las operaciones asincrónicas aseguran que nunca está bloqueando una acción porque una existente está en progreso. ASP.NET tiene un modelo asincrónico que permite que varias solicitudes se ejecuten una al lado de la otra. Sería posible poner en cola las solicitudes y procesarlas FIFO, pero esto no se escalaría bien cuando tenga cientos de solicitudes puestas en cola y cada solicitud requiera 100 ms para procesarse.
Si tiene un gran volumen de tráfico, es mejor que no realice sus consultas de forma asíncrona, ya que puede que no haya recursos adicionales para atender las solicitudes . Si no hay recursos sobrantes, sus solicitudes se ven obligadas a hacer cola, demorarse exponencialmente o fallar por completo, en cuyo caso la sobrecarga asíncrona (mutexes y operaciones de cambio de contexto) no le da nada.
En lo que respecta a ASP.NET, no tiene elección: utiliza un modelo asíncrono, porque eso es lo que tiene sentido para el modelo de servidor-cliente. Si tuviera que escribir internamente su propio código que utiliza un patrón asíncrono para intentar escalar mejor, a menos que intente administrar un recurso compartido entre todas las solicitudes, en realidad no verá ninguna mejora porque ya está envuelto. en un proceso asincrónico que no bloquea nada más.
En definitiva, todo es subjetivo hasta que realmente veas lo que está causando un cuello de botella en tu sistema. A veces es obvio dónde un patrón asíncrono ayudará (evitando un bloqueo de recursos en cola). En última instancia, solo medir y analizar un sistema puede indicar dónde puede obtener eficiencias.
Editar:
En su ejemplo, la llamada Task.Factory.StartNew
pondrá en cola una operación en .NET thread-pool. La naturaleza de los subprocesos de Thread Pool se debe reutilizar (para evitar el costo de crear / destruir muchos subprocesos). Una vez que la operación finaliza, el hilo se libera al grupo para ser reutilizado por otra solicitud (el recolector de basura no se involucra realmente a menos que haya creado algunos objetos en sus operaciones, en cuyo caso se recopilan como de costumbre alcance).
En lo que respecta a ASP.NET, no hay una operación especial aquí. La solicitud ASP.NET se completa sin respeto a la tarea asincrónica. La única preocupación podría ser si su grupo de subprocesos está saturado (es decir, no hay subprocesos disponibles para atender la solicitud en este momento y la configuración del grupo no permite que se creen más subprocesos), en cuyo caso la solicitud está bloqueada esperando para iniciar el tarea hasta que un subproceso de grupo esté disponible.
Lo primero es que no es MVC sino el IIS que mantiene el grupo de subprocesos. Por lo tanto, cualquier solicitud que llegue a la aplicación MVC o ASP.NET se publica desde hilos que se mantienen en el grupo de subprocesos. Solo al hacer la aplicación Asynch invoca esta acción en un hilo diferente y lanza el hilo inmediatamente para que se puedan realizar otras solicitudes.
He explicado lo mismo con un video de detalles ( http://www.youtube.com/watch?v=wvg13n5V0V0/ "Controladores MVC Asynch e inanición de hilos") que muestra cómo se produce la falta de hilo en MVC y cómo se minimiza al usar MVC Controladores Asynch. También he medido las colas de solicitudes utilizando perfmon para que pueda ver cómo se disminuyen las colas de solicitudes para el asincronismo MVC y cuál es el peor para las operaciones de Sincronización.
Sí, todos los hilos provienen del grupo de hilos. Su aplicación MVC ya tiene varios hilos, cuando llega una solicitud, se toma un nuevo hilo del grupo y se utiliza para dar servicio a la solicitud. Ese hilo estará ''bloqueado'' (a partir de otras solicitudes) hasta que se complete el servicio y se complete la solicitud. Si no hay ningún subproceso disponible en el grupo, la solicitud deberá esperar hasta que esté disponible.
Si tiene controladores asíncronos, aún reciben un hilo del grupo, pero mientras atienden la solicitud pueden abandonar el hilo, mientras esperan que algo suceda (y ese hilo puede asignarse a otra solicitud) y cuando la solicitud original necesita un hilo nuevamente obtiene uno de la piscina.
La diferencia es que si tiene muchas solicitudes de larga ejecución (donde el hilo espera una respuesta de algo) es posible que se agoten los hilos del grupo para atender incluso las solicitudes básicas. Si tiene controladores asíncronos, no tiene más subprocesos, pero esos subprocesos que están en espera se devuelven al grupo y pueden atender otras solicitudes.
Un ejemplo de vida casi real ... Piense que es como subirse al autobús, hay cinco personas esperando para subir, el primero se sube, paga y se sienta (el conductor atendió su pedido), se sube (el conductor está prestando servicio su solicitud) pero no puede encontrar su dinero; mientras hurga en sus bolsillos, el conductor se da por vencido y obtiene a las dos personas siguientes (atendiendo sus solicitudes), cuando encuentra su dinero, el conductor comienza a tratar con usted nuevamente (completando su pedido) - la quinta persona tiene que esperar hasta Ya terminaste, pero la tercera y la cuarta persona fueron atendidas mientras estabas a la mitad del tiempo para que te atendieran. Esto significa que el controlador es el único hilo del grupo y los pasajeros son las solicitudes. Era demasiado complicado escribir cómo funcionaría si hubiera dos controladores, pero te puedes imaginar ...
Sin un controlador asíncrono, los pasajeros detrás de ti tendrían que esperar años mientras buscabas tu dinero, mientras tanto el conductor del autobús no estaría trabajando.
Entonces, la conclusión es que si muchas personas no saben dónde está su dinero (es decir, requieren mucho tiempo para responder a algo que el conductor les haya preguntado), los controladores asíncronos podrían ayudar a procesar solicitudes, acelerando el proceso de algunas. Sin un controlador de aysnc, todos esperan hasta que la persona de enfrente haya sido completamente tratada. PERO no olvide que en MVC tiene muchos conductores de autobús en un solo bus, por lo que la sincronización no es una opción automática.
Sí, usan un hilo del grupo de subprocesos. En realidad, existe una excelente guía de MSDN que abordará todas sus preguntas y más. He encontrado que es bastante útil en el pasado. ¡Echale un vistazo!
http://msdn.microsoft.com/en-us/library/ee728598.aspx
Mientras tanto, los comentarios + sugerencias que escuche sobre el código asíncrono deben tomarse con un grano de sal. Para empezar, el hecho de hacer algo asincrónico no necesariamente lo hace escalar mejor, y en algunos casos puede empeorar su aplicación. El otro comentario que publicó sobre "un gran volumen de tráfico ..." también es correcto solo en ciertos contextos. Realmente depende de lo que estén haciendo sus operaciones y de cómo interactúan con otras partes del sistema.
En resumen, muchas personas tienen muchas opiniones sobre asincronización, pero pueden no estar correctas fuera de contexto. Diría que se centren en sus problemas exactos y realicen pruebas básicas de rendimiento para ver qué controladores asíncronos, etc. realmente manejan con su aplicación.