In realtà, asincrono / aspettano non è così magico. L'intero argomento è piuttosto ampio, ma per una risposta abbastanza rapida ma completa alla tua domanda penso che possiamo farcela.
Affrontiamo un semplice evento di clic sul pulsante in un'applicazione Windows Form:
public async void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before awaiting");
await GetSomethingAsync();
Console.WriteLine("after awaiting");
}
Io vado a esplicitamente non parlare di ciò si GetSomethingAsync
sta tornando per ora. Diciamo solo che questo è qualcosa che si completerà dopo, diciamo, 2 secondi.
In un mondo tradizionale, non asincrono, il gestore di eventi di clic sui pulsanti sarebbe simile al seguente:
public void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before waiting");
DoSomethingThatTakes2Seconds();
Console.WriteLine("after waiting");
}
Quando si fa clic sul pulsante nel modulo, l'applicazione sembrerà bloccarsi per circa 2 secondi, mentre attendiamo il completamento di questo metodo. Quello che succede è che il "pump dei messaggi", sostanzialmente un loop, è bloccato.
Questo ciclo chiede continuamente a Windows "Qualcuno ha fatto qualcosa, come spostare il mouse, fare clic su qualcosa? Devo ridipingere qualcosa? Se sì, dimmelo!" e quindi elabora quel "qualcosa". Questo ciclo ha ricevuto un messaggio che l'utente ha fatto clic su "button1" (o il tipo equivalente di messaggio da Windows) e ha finito per chiamare il nostro button1_Click
metodo sopra. Fino a quando questo metodo non ritorna, questo loop è ora bloccato in attesa. Questo richiede 2 secondi e durante questo, nessun messaggio viene elaborato.
La maggior parte delle cose che si occupano di Windows sono fatte usando i messaggi, il che significa che se il ciclo di messaggi smette di pompare i messaggi, anche solo per un secondo, è rapidamente evidente dall'utente. Ad esempio, se sposti il blocco note o qualsiasi altro programma sopra il tuo programma e poi di nuovo via, una serie di messaggi di disegno vengono inviati al tuo programma indicando quale regione della finestra che ora è diventata improvvisamente di nuovo visibile. Se il ciclo di messaggi che elabora questi messaggi è in attesa di qualcosa, bloccato, allora non viene eseguito alcun disegno.
Quindi, se nel primo esempio, async/await
non crea nuovi thread, come lo fa?
Bene, quello che succede è che il tuo metodo è diviso in due. Questo è uno di quegli argomenti di ampio tipo, quindi non entrerò in troppi dettagli ma basti dire che il metodo è suddiviso in queste due cose:
- Tutto il codice che porta a
await
, compresa la chiamata aGetSomethingAsync
- Tutto il codice seguente
await
Illustrazione:
code... code... code... await X(); ... code... code... code...
riarrangiato:
code... code... code... var x = X(); await X; code... code... code...
^ ^ ^ ^
+---- portion 1 -------------------+ +---- portion 2 ------+
Fondamentalmente il metodo viene eseguito in questo modo:
- Esegue tutto fino a
await
Chiama il GetSomethingAsync
metodo, che fa la sua cosa, e restituisce qualcosa che completerà 2 secondi in futuro
Finora siamo ancora all'interno della chiamata originale a button1_Click, che si verifica sul thread principale, chiamato dal loop dei messaggi. Se il codice che precede await
richiede molto tempo, l'interfaccia utente verrà comunque bloccata. Nel nostro esempio, non così tanto
Quello che la await
parola chiave, insieme ad una magica intelligenza del compilatore, è che fondamentalmente è qualcosa del tipo "Ok, sai cosa, tornerò semplicemente dal gestore di eventi clic sul pulsante qui. Quando tu (come in, la cosa che sto aspettando) vai al completamento, fammi sapere perché mi resta ancora del codice da eseguire ".
In realtà, farà sapere alla classe SynchronizationContext che è fatto, che, a seconda del contesto di sincronizzazione attuale che è in gioco in questo momento, farà la fila per l'esecuzione. La classe di contesto utilizzata in un programma Windows Form la metterà in coda utilizzando la coda che il ciclo di messaggi sta pompando.
Quindi ritorna al ciclo dei messaggi, che ora è libero di continuare a pompare i messaggi, come spostare la finestra, ridimensionarla o fare clic su altri pulsanti.
Per l'utente, l'interfaccia utente ora è di nuovo reattiva, elaborando altri clic sui pulsanti, ridimensionando e, soprattutto, ridisegnando , quindi non sembra bloccarsi.
- 2 secondi dopo, la cosa che stiamo aspettando viene completata e ciò che accade ora è che (beh, il contesto di sincronizzazione) inserisce un messaggio nella coda che il ciclo di messaggi sta guardando, dicendo "Ehi, ho dell'altro codice per eseguire ", e questo codice è tutto il codice dopo l'attesa.
- Quando il ciclo di messaggi arriva a quel messaggio, fondamentalmente "reinserirà" quel metodo da cui era stato interrotto, subito dopo
await
e continuando a eseguire il resto del metodo. Si noti che questo codice viene nuovamente chiamato dal ciclo di messaggi, quindi se questo codice fa qualcosa di lungo senza usarlo async/await
correttamente, bloccherà nuovamente il ciclo di messaggi
Ci sono molte parti mobili sotto il cofano qui, quindi qui ci sono alcuni collegamenti a ulteriori informazioni, stavo per dire "se ne avessi bisogno", ma questo argomento è piuttosto ampio ed è abbastanza importante conoscere alcune di quelle parti mobili . Invariabilmente capirai che async / wait è ancora un concetto che perde. Alcune delle limitazioni e dei problemi sottostanti continuano a insinuarsi nel codice circostante e, in caso contrario, di solito si finisce per eseguire il debug di un'applicazione che si interrompe in modo casuale per apparentemente senza una buona ragione.
OK, quindi cosa succede se GetSomethingAsync
gira un thread che verrà completato in 2 secondi? Sì, allora ovviamente c'è un nuovo thread in gioco. Questo thread, tuttavia, non è a causa dell'asincrono di questo metodo, ma perché il programmatore di questo metodo ha scelto un thread per implementare il codice asincrono. Quasi tutti gli I / O asincroni non usano un thread, usano cose diverse. async/await
da soli non creano nuovi thread ma ovviamente le "cose che aspettiamo" possono essere implementate usando i thread.
Ci sono molte cose in .NET che non fanno necessariamente girare un thread da sole ma sono ancora asincrone:
- Richieste Web (e molte altre cose relative alla rete che richiedono tempo)
- Lettura e scrittura di file asincroni
- e molti altri, un buon segno è se la classe / interfaccia in questione ha metodi denominati
SomethingSomethingAsync
o BeginSomething
ed EndSomething
e c'è un IAsyncResult
coinvolto.
Di solito queste cose non usano un filo sotto il cappuccio.
OK, quindi vuoi alcune di quelle "cose a tema ampio"?
Bene, chiediamo a Prova Roslyn del nostro clic sul pulsante:
Prova Roslyn
Non ho intenzione di collegarmi alla classe completamente generata qui, ma è roba piuttosto cruenta.