2015-07-06 36 views
7

Po przeczytaniu this post kilka miesięcy temu, stałem się paranoikiem na uzyskanie Result z Task<T> i nieustannie pakowałem wszystkie moje połączenia do niego za pomocą ConfigureAwait(false) lub Task.Run. Jednak z jakiegoś powodu poniższy kod zakończy się pomyślnie:Dlaczego nie wywoływanie zadania <T>.

public static void Main(string[] args) 
{ 
    var arrays = DownloadMany(); 

    foreach (var array in arrays); 
} 

IEnumerable<byte[]> DownloadMany() 
{ 
    string[] links = { "http://google.com", "http://microsoft.com", "http://apple.com" }; 

    using (var client = new HttpClient()) 
    { 
     foreach (var uri in links) 
     { 
      Debug.WriteLine("Still here!"); 
      yield return client.GetByteArrayAsync(uri).Result; // Why doesn't this deadlock? 
     } 
    } 
} 

drukuje kod Still here! 3 razy, a następnie kończy działanie. Czy to jest specyficzne dla HttpClient, że można bezpiecznie zadzwonić pod numer Result (jak u ludzi, którzy go napisali, zasypali go ConfigureAwait(false))?

+0

przy okazji ... znacznie łatwiej jest nie blokować i używać w razie potrzeby czekania, niż wszędzie gdzie jest "ConfigureAwait". – i3arnon

+0

@ i3arnon To prawda, ale piszę interfejs API obsługujący zarówno synchroniczne, jak i asynchroniczne wywoływania. –

+1

@JamesKo: Zalecam, aby metody asynchroniczne udostępniały asynchroniczne interfejsy API. Jeśli jednak * musisz * obsługiwać zarówno synchronizację, jak i asynchronię (np. W celu zapewnienia kompatybilności wstecznej), możesz znaleźć mój ostatni [artykuł MSDN na temat asynchronicznego kontekstu brownfield] (https://msdn.microsoft.com/en-us/magazine/ mt238404.aspx) przydatne. –

Odpowiedz

10

Task.Result będzie blokował tylko w obecności określonych SynchronizationContext s. W aplikacjach konsolowych nie ma jednej, więc kontynuacja jest zaplanowana na ThreadPool. Tak jak w przypadku korzystania z ConfigureAwait(false).

Na przykład w wątkach UI jest taki, który planuje kontynuacje do pojedynczego wątku interfejsu użytkownika. Jeśli czekasz synchronicznie z Task.Result przy użyciu wątku interfejsu użytkownika, w zadaniu, które można wykonać tylko w wątku interfejsu użytkownika, masz zakleszczenie.

Co więcej, impas zależy od implementacji GetByteArrayAsync. Możesz zakleszczać tylko wtedy, gdy jest to metoda asynchroniczna, a jej oczekiwanie nie będzie wymagało użycia ConfigureAwait(false).

Jeśli chcesz, możesz użyć Stephen Cleary AsyncContext, który dodaje odpowiednią SynchronizationContext do aplikacji konsolowej, aby sprawdzić, czy Twój kod może blokować aplikacje (lub ASP.Net).


O HttpClient „s (i większość .NET'S) zadaniowy powracający metody: oni nie są technicznie asynchroniczny. Nie używają one słów kluczowych: async i await. Zwracają po prostu zadanie. Zwykle opakowanie nad Task.Factory.FromAsync. Więc to prawdopodobnie "bezpieczne" blokowanie ich.

+1

To bardzo dziwne. Myślałem, że to tylko dotyczy asynchronizacji/czekania. Nie ma tu kontynuacji, więc nie rozumiem, co zostanie zaplanowane w innym wątku. Co więcej, jeśli "Wynik" * nie blokuje *, jak sugerujesz, jaka jest metoda zwracania? Puste tablice? –

+2

@Asad Wynik jest blokowany. Po prostu nie blokuje pojedynczego wątku interfejsu użytkownika (ponieważ nie jest to aplikacja interfejsu użytkownika). Blokuje główny wątek. – i3arnon

+1

@ i3arnon Nie jest jasne, o czym mówisz w interfejsie użytkownika. Chodzi mi o to, że 'Wynik' zawsze będzie blokował, niezależnie od dostępnego kontekstu synchronizacji. Jeśli metoda zwraca 'zadanie' czegoś, to coś musi zostać rozwiązane, zanim pochłaniacz' Task.Result' zwróci ci wartość. To, czy działa w aplikacji UI, czy w aplikacji konsolowej, jest nieistotne, przynajmniej dla mnie. –

Powiązane problemy