2015-04-01 18 views
5

Po przeczytaniu Stephen Toub's article on SynchronizationContext ja zostaję z pytaniem o wyjściu tego kawałka kodu .NET 4.5:SynchronizationContext płynie na Task.Run ale nie czekają

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

wyjście jest:

btnDoSomething_Click WindowsFormsSynchronizationContext

DoItAsync WindowsFormsSynchronizationContext

PerformServiceCall 1 WindowsFormsSynchronizationContext

PerformServiceCall 2 ThreadPoolTaskScheduler

ProcessMessage ThreadPoolTaskScheduler

PerformServiceCall 3 ThreadPoolTaskScheduler

Ale spodziewałbym PerformServiceCall 1, aby nie być na WindowsFormsSynchronizationContext ponieważ artykuł stwierdza, że ​​„SynchronizationContext. Prąd nie "płynie" w oczekiwaniu na punkty "...

kontekst nie przejdzie Dzwoniąc PerformServiceCall z Task.Run i lambda asynchroniczny, tak:

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

Może ktoś wyjaśnić lub punkt do pewnego dokumentacji na ten temat?

+1

Wywołanie ConfigureAwait() nie będzie miało żadnego wpływu, dopóki zadanie faktycznie nie zacznie czekać. To się jeszcze nie stało, twoje wywołanie LogSyncContext() było wcześniej. Przenieś go po oczekiwaniu. –

+0

Czy to nie jest zakleszczenie w 'DoItAsync(). Czekać();'? –

+0

Nie, to nie jest zakleszczenie dzięki wywołaniu ConfigureAwait – Stif

Odpowiedz

6

Artykuł Stephena wyjaśnia, że ​​SynchronizationContext nie "przepływa" jako ExecutionContext (chociaż SynchronizationContext jest częścią ExecutionContext).

ExecutionContext zawsze płynie. Nawet jeśli użyjesz Task.Run, więc gdyby SynchronizationContext przepłynął z nim, to Task.Run byłby wykonany na wątku UI, więc Task.Run byłoby bezcelowe. SynchronizationContext nie płynie, jest raczej przechwytywany po osiągnięciu punktu asynchronicznego (tj. await) i kontynuacji po wysłaniu do niego (chyba że wyraźnie zaznaczono inaczej).

Różnica jest wyjaśnione w tym cytat:

Teraz mamy bardzo ważne spostrzeżenie, aby: płynący ExecutionContext jest semantycznie bardzo różni się od przechwytywania i wpis do SynchronizationContext.

Po przepłynięciu ExecutionContext przechwytujesz stan z jednego wątku, a następnie przywracasz stan taki, że jest w otoczeniu podczas wykonywania dostarczonego delegata. Nie dzieje się tak, gdy przechwytujesz i używasz SynchronizationContext. Część przechwytująca jest taka sama, ponieważ pobierasz dane z bieżącego wątku, ale potem używasz tego stanu w inny sposób. Zamiast ustawiania tego stanu podczas inwokacji delegata, przy użyciu SynchronizationContext.Post po prostu używasz tego przechwyconego stanu, aby wywołać delegata. Gdzie i kiedy i jak działa ten delegat, zależy całkowicie od wdrożenia metody Post.

Oznacza to, że w przypadku, gdy wyjście PerformServiceCall 1 Obecny SynchronizationContext jest rzeczywiście WindowsFormsSynchronizationContext ponieważ nie osiągnęły jeszcze żadnego punktu asynchroniczny i nadal jesteś w wątku UI (należy pamiętać, że część przed pierwszą await w metodzie async jest wykonywana synchronicznie na wątku wywołującym, więc LogSyncContext("PerformServiceCall 1"); dzieje się przed ConfigureAwait(false) dzieje się w zadaniu zwróconym z PerformServiceCall).

"Wysiadasz" z interfejsu użytkownika SynchronizationContext, gdy używasz ConfigureAwait(false) (co lekceważy przechwycone SynchronizationContext). Po raz pierwszy zdarza się to na HttpClient.GetAsync, a następnie ponownie na PerformServiceCall.

Powiązane problemy