2015-09-14 6 views
33

Non capisco esattamente cosa stia succedendo dietro le quinte quando ho un'azione asincrona su un controller MVC, specialmente quando si tratta di operazioni di I/O. Diciamo che ho un azione di caricamento:Comprensione profonda di async/await su ASP.NET MVC

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

    ... 
} 

Da quello che so questi sono i passaggi fondamentali che accadono:

  1. un nuovo thread è sbirciò da threadpool e assegnata la gestione incomming richiesta.

  2. Quando l'attesa viene colpita, se la chiamata è un'operazione di I/O, il thread originale torna in pool e il controllo viene trasferito a un cosiddetto IOCP (porta di completamento dell'uscita di input). Quello che non capisco è perché la richiesta è ancora viva e aspetta una risposta perché alla fine il client chiamante aspetterà che la nostra richiesta venga completata.

La mia domanda è: chi/quando/come si verifica questa attesa per il blocco completo?

Nota: ho visto il post del blog There Is No Thread e ha senso per le applicazioni GUI, ma per questo scenario lato server non capisco. Veramente.

+1

Che cosa stai cercando esattamente: spiegazione di come regolare [IHttpAsyncHandler] (https://msdn.microsoft.com/en-us/library/system.web.ihttpasynchandler (v = vs.110) .aspx) si integra con 'async' /' await' o perché 'IHttpAsyncHandler' funziona o qualcos'altro? –

+1

Ho difficoltà a cercare di capire quale parte non capisci, puoi chiarire? Non vi è alcun motivo per cui la richiesta di morire se il thread di gestione viene reinserito nel pool: verrà solo ripristinato in un secondo momento, eventualmente su un altro thread. Il controllo BTW non è * trasferito * su un IOCP. –

risposta

21

Ci sono alcune buone risorse sulla rete che descrivono questo in dettaglio. Ho scritto un MSDN article that describes this at a high level.

Quello che non capisco è il motivo per cui la richiesta è ancora attiva e attende una risposta perché alla fine il client chiamante aspetterà che la nostra richiesta venga completata.

È ancora attivo perché il runtime ASP.NET non lo ha ancora completato. Completare la richiesta (inviando la risposta) è un'azione esplicita; non è come se la richiesta si completasse da sola. Quando ASP.NET rileva che l'azione del controller restituisce un valore Task/Task<T>, non completa la richiesta fino al completamento di tale attività.

La mia domanda è: chi/quando/come si verifica questa attesa per il blocco completo?

Niente è in attesa.

Pensateci in questo modo: ASP.NET ha una raccolta di richieste correnti che sta elaborando. Per una determinata richiesta, non appena completata, la risposta viene inviata e quindi tale richiesta viene rimossa dalla raccolta.

La chiave è che si tratta di una raccolta di richieste, non di thread. Ciascuna di queste richieste può avere o meno un thread su di esso in qualsiasi momento. Le richieste sincrone hanno sempre un singolo thread (lo stesso thread). Le richieste asincrone possono avere periodi in cui non hanno thread.

Nota: ho visto questo thread: http://blog.stephencleary.com/2013/11/there-is-no-thread.html e ha senso per le applicazioni GUI, ma per questo scenario lato server non capisco.

L'approccio threadless all'I/O funziona esattamente allo stesso modo per le app ASP.NET come per le app GUI.

Eventualmente, la scrittura del file verrà completata, che (eventualmente) completa l'attività restituita da ReadFile. Questo lavoro di "completamento del compito" viene normalmente eseguito con un thread pool di thread. Poiché l'attività è stata completata, l'azione Upload continuerà l'esecuzione, facendo in modo che quel thread inserisca il contesto della richiesta (ovvero, ora c'è un thread che esegue nuovamente la richiesta). Quando il metodo Upload è completo, l'attività restituita da Upload è completa e ASP.NET scrive la risposta e rimuove la richiesta dalla sua raccolta.

+2

"La chiave è che si tratta di una raccolta di richieste, non di thread, ognuna delle quali può avere o meno thread in lavorazione in qualsiasi momento. Le richieste sincrone hanno sempre un singolo thread (stesso thread).Le richieste asincrone possono avere periodi in cui non sono presenti thread. "Lo cancella bene. – Sully

8

Il runtime di ASP.NET comprende quali attività sono e ritarda l'invio della risposta HTTP fino al completamento dell'attività. Infatti, il valore è necessario per generare anche una risposta.

Il runtime fa fondamentalmente questo:

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

Così, quando il vostro await è colpito sia il codice e il codice runtime ottiene dallo stack e "non c'è filo" in quel punto. La richiamata ContinueWith ripristina la richiesta e invia la risposta.

10

Sotto il cofano, il compilatore offre un gioco di prestigio e trasforma il tuo codice async \ await in un codice basato su Task con un callback. Nel caso più semplice:

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

viene cambiato in qualcosa di simile:

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

Quindi non c'è nessuna magia - solo un sacco di Task s e callback. Per codice più complesso le trasformazioni saranno anche più complesse, ma alla fine il codice risultante sarà logicamente equivalente a ciò che hai scritto. Se vuoi, puoi prendere uno di ILSpy/Reflector/JustDecompile e vedere da te cosa viene compilato "under the hood".

L'infrastruttura ASP.NET MVC a sua volta è abbastanza intelligente da riconoscere se il metodo di azione è normale o uno basato su Task e modificare a sua volta il proprio comportamento. Pertanto la richiesta non "scompare".

Un comune malinteso è che tutto con async genera un altro thread. In realtà, è principalmente l'opposto. Alla fine della lunga catena dei metodi async Task normalmente c'è un metodo che esegue un'operazione di I/O asincrona (come la lettura dal disco o la comunicazione via rete), che è una cosa magica eseguita da Windows stesso.Per la durata di questa operazione, non vi è alcun filo associato al codice - è effettivamente fermato. Al termine dell'operazione, Windows richiama e viene quindi assegnato un thread dal pool di thread per continuare l'esecuzione. C'è un po 'di codice framework coinvolto per preservare lo HttpContext della richiesta, ma questo è tutto.