2015-05-05 15 views
7

Ho appena provato qualcosa che ero sicuro che sarebbe fallito miseramente, ma con mia sorpresa, ha funzionato perfettamente, e dimostra a me stesso che sono ancora abbastanza disorientato da come funziona async-await.Async thread body loop, funziona, ma come?

Ho creato un thread, passando un delegato async void come corpo del thread. Ecco una semplificazione eccessiva del mio codice:

var thread = new Thread(async() => { 
    while(true) { 
     await SomeLengthyTask(); 
     ... 
    } 
}); 
thread.Start(); 
thread.Join(); 

Il fatto è che, per quanto ho capito, quando l'esecuzione colpisce la parola await, c'è un rendimento implicito dal metodo, in questo caso il corpo del looping thread, mentre il resto del codice è avvolto in una continuazione di callback.

A causa di questo fatto, ero abbastanza sicuro che il thread sarebbe terminato non appena il await ha dato esecuzione, ma non è il caso!

Qualcuno sa come questa magia sia effettivamente implementata? La funzionalità async è ridotta in ordine e l'async attende in modo sincrono oppure è in corso una magia nera da parte del CLR che consente di riprendere un thread che ha restituito uno await?

risposta

7

Il thread è effettivamente terminato molto rapidamente.

Ma dal momento che il costruttore Thread non accetta un lambda async, quello che si ottiene è un delegato async void.

La discussione originale termina e la continuazione (il resto dopo lo await) verrà registrata su ThreadPool ed eventualmente eseguita su un altro thread.

È possibile verificare che controllando l'ID:

var thread = new Thread(async() => 
{ 
    while (true) 
    { 
     Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 
     await SomeLengthyTask(); 
     Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 
    } 
}); 
thread.Start(); 
thread.Join(); 

Console.ReadLine(); 

uscita:

3 
5 
5 
... 
5 

Per rendere l'esempio più semplice supponiamo di avere questo metodo Run:

void Run(Action action) 
{ 
    action(); 
} 

E lo chiami con il tuo delegato async

Run(async() => 
{ 
    while(true) 
    { 
     await SomeLengthyTask(); 
     ... 
    } 
}); 

L'esecuzione di Run completerà quasi immediatamente quando raggiunge il primo await e ritorna. Il resto del delegato async continuerà su ThreadPool con un altro thread.


Generalmente, ogni volta che si raggiunge un await nell'esecuzione di un metodo async il filo si perde e la continuazione (il resto dopo l'operazione atteso completa) sarà pubblicato al ThreadPool (a meno che se c'è uno SynchronizationContext presente, come nel thread dell'interfaccia utente). Potrebbe essere che l'esecuzione sia sullo stesso thread (come nel mio esempio con 5) ma potrebbe anche non esserlo.

Nel tuo caso il thread creato in modo esplicito non fa parte di ThreadPool, quindi verrà definitivamente terminato e il resto verrà eseguito su un thread diverso.

+0

Grazie per il corretto chiarimento tra lambda e delegato, ho modificato la mia domanda di conseguenza. Quindi, per capire, ciò che il CLR sta effettivamente facendo è semplicemente impedire che il join() del thread originale abbia luogo? – BlueStrat

+2

@BlueStrat No, ha luogo. Questo è il motivo per cui ho aggiunto un "Console.ReadLine" nel mio esempio. Senza di essa la mia applicazione per console termina prima di raggiungere la parte interessante. – i3arnon

+1

@BlueStrat Non c'è niente di speciale da fare qui. I metodi 'async' semplicemente non funzionano sullo stesso thread. Possono farlo, ma fino a quando non c'è un SynchronizationContext eseguono ogni parte sincrona sul ThreadPool – i3arnon