2015-04-01 8 views
5

Nach Stephen Toub's article on SynchronizationContext Lesen ich mit einer Frage nach links über den Ausgang dieses Stück 4.5 Code .NET:SynchronizationContext fließt auf Task.Run aber nicht erwarten auf

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)); 
} 

Die Ausgabe lautet:

btnDoSomething_Click WindowsFormsSynchronizationContext

DoItAsync WindowsFormsSynchronizationContext

PerformServiceCall 1 WindowsFormsSynchronizationContext

PerformServiceCall 2 ThreadPoolTaskScheduler

Process ThreadPoolTaskScheduler

PerformServiceCall 3 ThreadPoolTaskScheduler

Aber ich würde erwarten PerformServiceCall 1 nicht auf dem WindowsFormsSynchronizationContext zu sein, da der Artikel besagt, dass „SynchronizationContext. Der Strom fließt nicht über die Wartepunkte "...

Der Kontext nicht bestanden wird erhalten, wenn PerformServiceCall mit Task.Run und einem Asynchron-lambda, wie dies Aufruf:

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

auf dem bis zu einem gewissen Dokumentation Kann jemand klären oder Punkt?

+1

Der ConfigureAwait() - Aufruf hat keine Auswirkungen, bis der Task tatsächlich zu warten beginnt. Das ist noch nicht geschehen, Ihr LogSyncContext() - Aufruf war zu früh. Verschiebe es nach dem Warten. –

+0

Ist das nicht Deadlock auf 'DoItAsync(). Wait();'? –

+0

Nein, es ist kein Deadlock dank des ConfigureAwait-Aufrufs – Stif

Antwort

6

Stephen's Artikel erklärt, dass SynchronizationContext nicht "fließt", wie ExecutionContext tut (obwohl SynchronizationContext ist ein Teil der ExecutionContext).

ExecutionContext wird immer geflossen. Selbst wenn Sie Task.Run verwenden, also wenn mit ihm fließen würde Task.Run würde auf dem UI-Thread ausführen und so Task.Run wäre sinnlos. SynchronizationContext fließt nicht, es wird vielmehr erfasst, wenn ein asynchroner Punkt (d. H. await) erreicht wird und die Fortsetzung, nachdem es darauf gepostet wurde (sofern nicht explizit anders angegeben).

Die Differenz wird in diesem Zitat erklärt:

Jetzt haben wir eine sehr wichtige Beobachtung zu machen: ExecutionContext fließt semantisch ganz anders ist, als die Erfassung und Buchung auf ein SynchronizationContext.

Wenn Sie ExecutionContext gehen, erfassen Sie den Status von einem Thread und stellen diesen Status dann so wieder her, dass er während der Ausführung des angegebenen Delegaten den Umgebungsstatus wiederherstellt. Das passiert nicht, wenn Sie eine SynchronizationContext erfassen und verwenden. Der Erfassungsteil ist der gleiche, indem Sie Daten aus dem aktuellen Thread erfassen, diesen Status jedoch anders verwenden. Anstatt diesen Status während des Aufrufs des Delegaten aktuell zu machen, verwenden Sie einfach diesen erfaßten Status mit SynchronizationContext.Post, um den Delegaten aufzurufen. Wo und wann und wie dieser Delegat ausgeführt wird, hängt vollständig von der Implementierung der Post-Methode ab.

, die in Ihrem Fall bedeutet, dass bei der Ausgabe PerformServiceCall 1 Die aktuellen SynchronizationContext in der Tat ist WindowsFormsSynchronizationContext, weil Sie noch keinen asynchronen Punkt erreicht haben, und Sie sind immer noch in dem UI-Thread (denken Sie daran, dass der Teil vor dem ersten await in einer async Methode wird synchron auf den aufrufenden Thread ausgeführt, so LogSyncContext("PerformServiceCall 1"); passiert, bevor ConfigureAwait(false) geschieht auf die Aufgabe von PerformServiceCall zurückgegeben).

Sie "nur" aus der Benutzeroberfläche SynchronizationContext UI, wenn Sie ConfigureAwait(false) verwenden (die ignoriert die erfassten SynchronizationContext). Das erste Mal, dass dies passiert, ist auf HttpClient.GetAsync und dann wieder auf PerformServiceCall.