asp.net web api - tutorial - Métodos asincrónicos de ApiController: ¿cuál es el beneficio? ¿Cuándo usar?
web api query string parameters (4)
(Esto probablemente duplica la pregunta del controlador ASP.NET MVC4 Async - ¿Por qué usar?, Pero sobre webapi, y no estoy de acuerdo con las respuestas allí)
Supongamos que tengo una solicitud de SQL de larga ejecución. Sus datos deben ser serializados a JSON y enviados al navegador (como respuesta a la solicitud xhr). Código de muestra:
public class DataController : ApiController
{
public Task<Data> Get()
{
return LoadDataAsync(); // Load data asynchronously?
}
}
Lo que realmente sucede cuando hago $ .getJson (''api / data'', ...) (mira este póster http://www.asp.net/posters/web-api/ASP.NET-Web-API-Poster.pdf ):
- [IIS] La solicitud es aceptada por IIS.
- [IIS] IIS espera un subproceso [THREAD] del grupo administrado ( http://msdn.microsoft.com/en-us/library/0ka9477y(v=vs.110).aspx ) y comienza a trabajar en él.
- [THREAD] Webapi crea un nuevo objeto DataController en esa cadena y otras clases.
- [THREAD] Utiliza lib de tarea paralela para iniciar una consulta sql en [THREAD2]
- [THREAD] vuelve a la agrupación administrada, lista para otro procesamiento
- [THREAD2] funciona con el controlador sql, lee los datos mientras está listo e invoca [THREAD3] para responder a la solicitud xhr
- [THREAD3] envía respuesta.
Por favor, siéntete libre de corregirme, si hay algo mal.
En la pregunta anterior, dicen, el punto y la ganancia es que [THREAD2] no es de The Managed Pool; sin embargo, el artículo de MSDN (enlace arriba) dice que
De forma predeterminada, los tipos de bibliotecas paralelas como
Task
yTask<TResult>
usan subprocesos de grupo de subprocesos para ejecutar tareas.
Entonces, llego a la conclusión de que los TRES HILOS son del grupo administrado.
Además, si utilizo el método sincrónico, aún mantendría mi servidor receptivo, utilizando solo un hilo (del grupo de subprocesos preciosos).
Entonces, ¿cuál es el verdadero punto de intercambio de 1 hilo a 3 hilos? ¿Por qué no solo maximizar los hilos en el grupo de subprocesos?
¿Hay alguna forma claramente útil de usar controladores asíncronos?
Creo que el malentendido clave está relacionado con el funcionamiento de las tareas async
. Tengo una introducción async
en mi blog que puede ayudar.
En particular, una Task
devuelta por un método async
no ejecuta ningún código. Por el contrario, es solo una manera conveniente de notificar a las personas que llaman sobre el resultado de ese método. Los documentos MSDN que citó solo se aplican a tareas que realmente ejecutan código, por ejemplo, Task.Run
.
Por cierto, el póster al que hizo referencia no tiene nada que ver con los hilos. Esto es lo que sucede en una solicitud de base de datos async
(ligeramente simplificada):
- IIS acepta la solicitud y la pasa a ASP.NET.
- ASP.NET toma uno de sus subprocesos de grupo de subprocesos y lo asigna a esa solicitud.
- WebApi crea
DataController
etc. - La acción del controlador inicia una consulta SQL asíncrona.
- El hilo de solicitud vuelve al grupo de subprocesos. No hay hilos procesando la solicitud.
- Cuando llega el resultado del servidor SQL, un hilo del grupo de subprocesos lee la respuesta.
- El subproceso del grupo de subprocesos notifica a la solicitud que está listo para continuar el proceso.
- Como ASP.NET sabe que no hay otros subprocesos que manejen esa solicitud, simplemente asigna el mismo subproceso a la solicitud para que pueda finalizarla directamente.
Si quiere un código de prueba de concepto, tengo un Gist antiguo que restringe artificialmente el grupo de subprocesos de ASP.NET al número de núcleos (que es su configuración mínima) y luego realiza N + 1 solicitudes síncronas y asíncronas. Ese código simplemente demora por un segundo en lugar de contactar a un servidor SQL, pero el principio general es el mismo.
El objetivo de la asincronización no es hacer que una aplicación tenga varios hilos, sino más bien dejar que una aplicación con un único hilo continúe con algo diferente en lugar de esperar una respuesta de una llamada externa que se está ejecutando en un hilo o proceso diferente.
Considere la posibilidad de una aplicación de escritorio que muestra los precios de las acciones de diferentes bolsas. La aplicación necesita hacer un par de llamadas REST / http para obtener algunos datos de cada uno de los servidores de bolsa de control remoto.
Una única aplicación con hilos haría la primera llamada, esperaría sin hacer nada hasta que obtuviera el primer conjunto de precios, actualizara su ventana, luego realizara una llamada al siguiente servidor de precios de acciones externo, nuevamente no haga nada hasta que obtenga los precios, actualice su ventana ... etc.
Podríamos lanzar todas las peticiones en paralelo en paralelo y actualizar la pantalla en paralelo, pero dado que la mayor parte del tiempo se gasta esperando una respuesta del servidor remoto, esto parece excesivo.
Puede ser mejor para el hilo: Hacer una solicitud para el primer servidor, pero en lugar de esperar la respuesta, deje un marcador, un lugar al que volver cuando lleguen los precios y pasar a emitir la segunda solicitud, dejando de nuevo un marcador de un lugar para volver a ... etc.
Cuando se han emitido todas las solicitudes, el hilo de ejecución de la aplicación puede seguir tratando con la entrada del usuario o lo que sea necesario.
Ahora, cuando se recibe una respuesta de uno de los servidores, el hilo se puede dirigir para continuar desde el marcador que estableció anteriormente y actualizar la ventana.
Todo lo anterior podría haberse codificado a mano larga, con un solo hilo, pero era tan horrible que el multihebra a menudo era más fácil. Ahora el proceso de dejar el marcador y regresar lo hace el compilador cuando escribimos async / await. Todo de rosca simple.
Hay dos puntos clave aquí:
1) ¡Aún hay subprocesos múltiples! El procesamiento de nuestra solicitud de precios de acciones ocurre en un hilo diferente (en una máquina diferente). Si estuviéramos haciendo acceso a DB, lo mismo sería cierto. En los ejemplos donde la espera es para un temporizador, el temporizador se ejecuta en un hilo diferente. Sin embargo, nuestra aplicación es de un solo hilo, el punto de ejecución solo salta (de forma controlada) mientras que los hilos externos se ejecutan
2) Perdemos el beneficio de la ejecución asincrónica tan pronto como la aplicación requiera una operación asincrónica para completarse. Considere una aplicación que muestre el precio del café de dos bolsas, la aplicación podría iniciar las solicitudes y actualizar sus ventanas de forma asíncrona en un único hilo, pero ahora si la aplicación también calculara la diferencia en el precio entre dos bolsas tendría que esperar la sincronización llamadas para completar. Esto se nos impone porque un método asíncrono (como uno que podríamos escribir para llamar a una bolsa por el precio de una acción) no devuelve el precio de la acción, sino una Tarea, que puede considerarse como una forma de volver al marcador que fue establecido para que la función pueda completar y devolver el precio de las acciones.
Esto significa que cada función que llama a una función asíncrona tiene que ser asincrónica o esperar a que se complete la llamada "otra secuencia / proceso / máquina" en la parte inferior de la pila de llamadas, y si estamos esperando que la llamada final se complete bien ¿Por qué molestarse con la asincronización?
Al escribir una API web, IIS u otro host es la aplicación de escritorio, escribimos nuestros métodos de controlador asincrónicos para que el host pueda ejecutar otros métodos en nuestro hilo para atender otras solicitudes mientras nuestro código está esperando una respuesta del trabajo en un hilo diferente / proceso / máquina.
En mi opinión, lo siguiente describe una clara ventaja de los controladores asíncronos sobre los síncronos.
Una aplicación web que utiliza métodos síncronos para atender llamadas de alta latencia donde el grupo de subprocesos crece hasta .NET 4.5 El máximo predeterminado de 5.000 subprocesos consumirá aproximadamente 5 GB más de memoria que una aplicación que pueda hacer el servicio con las mismas solicitudes utilizando métodos asincrónicos y solo 50 trapos. Cuando haces trabajo asincrónico, no siempre estás usando un hilo. Por ejemplo, cuando realiza una solicitud de servicio web asíncrona, ASP.NET no utilizará ningún subproceso entre la llamada al método asíncrono y la espera. El uso del grupo de subprocesos para atender solicitudes con alta latencia puede generar una gran huella de memoria y una utilización deficiente del hardware del servidor.
La ganancia de las acciones asíncronas es que mientras el controlador está esperando que termine la consulta sql no se asignan hilos para esta solicitud, mientras que si usó un método síncrono, un hilo estaría bloqueado en la ejecución de este método de principio a fin de ese método. Mientras SQL Server hace su trabajo, el hilo no hace otra cosa que esperar. Si utiliza métodos asíncronos, este mismo hilo puede responder a otras solicitudes mientras el servidor SQL está haciendo su trabajo.
Creo que sus pasos están equivocados en el paso 4, no creo que creará un nuevo hilo para hacer la consulta SQL. En 6 no se creó un nuevo hilo, es solo uno de los hilos disponibles que se usarán para continuar desde donde terminó el primer hilo. El hilo en 6 podría ser el mismo que comenzó la operación asincrónica.