without run practices method explained espaƱol ejemplos create best await async c# asp.net-mvc multithreading asynchronous async-await

run - create async method c#



Conocimiento profundo de async/await en ASP.NET MVC (3)

Debajo del capó, el compilador realiza un juego de manos y transforma su código async / await en un código basado en Task con una devolución de llamada. En el caso más simple:

public async Task X() { A(); await B(); C(); }

Se transforma en algo así como:

public Task X() { A(); return B().ContinueWith(()=>{ C(); }) }

Entonces no hay magia, solo muchas Task y devoluciones de llamadas. Para un código más complejo, las transformaciones también serán más complejas, pero al final el código resultante será lógicamente equivalente a lo que usted escribió. Si lo desea, puede tomar uno de ILSpy / Reflector / JustDecompile y ver por usted mismo lo que se compila "debajo del capó".

La infraestructura MVC de ASP.NET, a su vez, es lo suficientemente inteligente como para reconocer si su método de acción es uno normal, o uno basado en Task , y alterar su comportamiento sucesivamente. Por lo tanto, la solicitud no "desaparece".

Una idea errónea común es que todo con async genera otro hilo. De hecho, es todo lo contrario. Al final de la cadena larga de los métodos de async Task , normalmente hay un método que realiza algunas operaciones IO asíncronas (como leer desde el disco o comunicarse a través de la red), lo cual es algo mágico realizado por Windows mismo. Mientras dure esta operación, no hay ningún hilo asociado con el código en absoluto: se ha detenido efectivamente. Sin embargo, una vez completada la operación, Windows llama y luego se asigna un subproceso del grupo de subprocesos para continuar la ejecución. Hay un poco de código de marco involucrado para preservar el HttpContext de la solicitud, pero eso es todo.

No entiendo exactamente lo que ocurre detrás de las escenas cuando tengo una acción asíncrona en un controlador MVC, especialmente cuando se trata de operaciones de E / S. Digamos que tengo una acción de carga:

public async Task<ActionResult> Upload (HttpPostedFileBase file) { .... await ReadFile(file); ... }

Por lo que sé, estos son los pasos básicos que suceden:

  1. Se ve un nuevo subproceso desde el grupo de subprocesos y se le asigna para manejar la solicitud entrante.

  2. Cuando se activa la espera, si la llamada es una operación de E / S, el hilo original vuelve al grupo y el control se transfiere a un llamado IOCP (puerto de salida de salida de entrada). Lo que no entiendo es por qué la solicitud todavía está activa y espera una respuesta porque, al final, el cliente llamante esperará a que completemos nuestra solicitud.

Mi pregunta es: ¿Quién / cuándo / cómo espera esto para que ocurra el bloqueo completo?

Nota: vi la entrada del blog There Is No Thread , y tiene sentido para las aplicaciones GUI, pero para este escenario del lado del servidor no lo entiendo. De Verdad.


El tiempo de ejecución de ASP.NET comprende qué tareas son y demora el envío de la respuesta HTTP hasta que la tarea finalice. De hecho, el valor Task.Result es necesario para incluso generar una respuesta.

El tiempo de ejecución básicamente hace esto:

var t = Upload(...); t.ContinueWith(_ => SendResponse(t));

Entonces, cuando esté a la await se aplicará el código y el código de tiempos de ejecución se saldrá de la pila y "no habrá ningún hilo" en ese punto. La devolución de llamada ContinueWith revive la solicitud y envía la respuesta.


Hay algunos buenos recursos en la red que describen esto en detalle. Escribí un artículo de MSDN que describe esto en un alto nivel .

Lo que no entiendo es por qué la solicitud aún está activa y espera una respuesta porque al final el cliente llamante esperará a que completemos nuestra solicitud.

Todavía está activo porque el tiempo de ejecución de ASP.NET aún no lo ha completado. Completar la solicitud (enviando la respuesta) es una acción explícita; no es como si la solicitud se completara por sí misma. Cuando ASP.NET ve que la acción del controlador devuelve una Task / Task<T> , no completará la solicitud hasta que la tarea se complete.

Mi pregunta es: ¿Quién / cuándo / cómo espera esto para que ocurra el bloqueo completo?

Nada está esperando.

Piénselo de esta manera: ASP.NET tiene una colección de solicitudes actuales que está procesando. Para una solicitud determinada, tan pronto como esté completa, la respuesta se envía y luego esa solicitud se elimina de la colección.

La clave es que es una colección de solicitudes, no hilos. Cada una de esas solicitudes puede o no tener un hilo trabajando en ello en cualquier momento. Las solicitudes sincrónicas siempre tienen un único hilo (el mismo hilo). Las solicitudes asincrónicas pueden tener períodos en los que no tienen subprocesos.

Nota: vi este hilo: http://blog.stephencleary.com/2013/11/there-is-no-thread.html y tiene sentido para las aplicaciones GUI, pero para este escenario del lado del servidor no lo entiendo.

El enfoque sin rosca de E / S funciona exactamente igual para las aplicaciones ASP.NET que para las aplicaciones GUI.

Finalmente, se completará la escritura del archivo, que (finalmente) completa la tarea devuelta desde ReadFile . Este trabajo de "finalización de la tarea" normalmente se realiza con un hilo de grupo de subprocesos. Como la tarea ahora está completa, la acción Upload continuará ejecutándose, haciendo que ese hilo ingrese al contexto de la solicitud (es decir, ahora hay un hilo que ejecuta esa solicitud nuevamente). Cuando se completa el método Upload , se completa la tarea devuelta desde Upload , y ASP.NET escribe la respuesta y elimina la solicitud de su colección.