2015-04-01 4 views
5

Dopo aver letto Stephen Toub's article on SynchronizationContext sono lasciato con una domanda circa l'uscita di questo pezzo di .NET 4.5 Codice:SynchronizationContext scorre Task.Run ma non su attendono

private void btnDoSomething_Click() 
{ 
    LogSyncContext("btnDoSomething_Click"); 
    DoItAsync().Wait(); 
} 
private async Task DoItAsync() 
{ 
    LogSyncContext("DoItAsync"); 
    await PerformServiceCall().ConfigureAwait(false); //to avoid deadlocking 
} 
private async Task PerformServiceCall() 
{ 
    LogSyncContext("PerformServiceCall 1"); 
    HttpResponseMessage message = await new HttpClient 
    { 
     BaseAddress = new Uri("http://my-service") 
    } 
    .GetAsync("/").ConfigureAwait(false); //to avoid deadlocking 
    LogSyncContext("PerformServiceCall 2"); 
    await ProcessMessage(message); 
    LogSyncContext("PerformServiceCall 3"); 
} 

private async Task ProcessMessage(HttpResponseMessage message) 
{ 
    LogSyncContext("ProcessMessage"); 
    string data = await message.Content.ReadAsStringAsync(); 
    //do something with data 
} 

private static void LogSyncContext(string statementId) 
{ 
    Trace.WriteLine(String.Format("{0} {1}", statementId, SynchronizationContext.Current != null ? SynchronizationContext.Current.GetType().Name : TaskScheduler.Current.GetType().Name)); 
} 

L'output è:

btnDoSomething_Click WindowsFormsSynchronizationContext

DoItAsync WindowsFormsSynchronizationContext

PerformServiceCall 1 WindowsFormsSynchronizationContext

PerformServiceCall 2 ThreadPoolTaskScheduler

ProcessMessage ThreadPoolTaskScheduler

PerformServiceCall 3 ThreadPoolTaskScheduler

Ma mi sarei aspettato PerformServiceCall 1 di non essere sul WindowsFormsSynchronizationContext in quanto l'articolo afferma che "SynchronizationContext. La corrente non "scorre" attraverso i punti di attesa "...

Il contesto non viene passato al momento della chiamata PerformServiceCall con Task.Run ed un lambda asincrona, in questo modo:

await Task.Run(async() => 
{ 
    await PerformServiceCall(); 
}).ConfigureAwait(false); 

Qualcuno può chiarire o punto in una certa documentazione su questo?

+1

La chiamata ConfigureAwait() non avrà alcun effetto finché l'attività non inizia effettivamente ad attendere. Non è ancora successo, la tua chiamata a LogSyncContext() è stata anticipata. Spostalo dopo l'attesa. –

+0

Non è quello stallo su DoItAsync(). Wait(); '? –

+0

No, non è deadlocking grazie alla chiamata ConfigureAwait – Stif

risposta

6

L'articolo di Stephen spiega che SynchronizationContext non "scorre" come fa ExecutionContext (sebbene SynchronizationContext sia una parte di ExecutionContext).

ExecutionContext è sempre scorrevole. Anche se si utilizza Task.Run, quindi se SynchronizationContext dovesse scorrere con esso Task.Run si eseguirà sul thread dell'interfaccia utente e quindi Task.Run sarebbe inutile. SynchronizationContext non scorre, viene catturato piuttosto quando viene raggiunto un punto asincrono (ad esempio await) e il seguito viene postato su di esso (se non diversamente specificato).

La differenza è spiegata in questa citazione:

Ora, abbiamo un'osservazione molto importante fare: scorre ExecutionContext è semanticamente molto diverso da quello cattura e annuncio a un SynchronizationContext.

Quando si scorre ExecutionContext, si acquisisce lo stato da un thread e quindi si ripristina tale stato in modo che sia ambientale durante l'esecuzione del delegato fornito. Questo non è ciò che accade quando acquisisci e utilizzi uno SynchronizationContext. La parte di acquisizione è la stessa, in quanto stai acquisendo i dati dal thread corrente, ma in questo caso utilizzi quello stato in modo diverso. Anziché renderlo corrente durante l'invocazione del delegato, con lo SynchronizationContext.Post stai semplicemente utilizzando lo stato catturato per richiamare il delegato. Dove e quando e come viene eseguito quel delegato dipende completamente dall'implementazione del metodo Post.

Ciò significa che nel tuo caso che quando l'uscita PerformServiceCall 1 L'attuale SynchronizationContext è infatti WindowsFormsSynchronizationContext perché non è stato ancora raggiunto alcun punto asincrono e vi sono ancora nel thread UI (tenere a mente che la parte prima del primo await in un metodo async viene eseguito in modo sincrono sul thread chiamante in modo che LogSyncContext("PerformServiceCall 1"); avvenga prima dello ConfigureAwait(false) nell'attività restituita da PerformServiceCall.

È possibile "scattare" l'interfaccia utente SynchronizationContext quando si utilizza ConfigureAwait(false) (che ignora lo SynchronizationContext catturato). La prima volta che succede è su HttpClient.GetAsync e poi di nuovo su PerformServiceCall.