2015-07-06 16 views
7

Nach dem Lesen von this Post vor ein paar Monaten, wurde ich paranoid von der Result eines Task<T> bekommen und unaufhörlich alle meine Anrufe mit einer ConfigureAwait(false) oder Task.Run umwickelt. Doch aus irgendeinem Grund beendet der folgende Code erfolgreich:Warum ruft Task <T> .Result Deadlock nicht auf?

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? 
     } 
    } 
} 

Die Code druckt Still here! 3 mal und dann beendet. Ist das spezifisch für HttpClient, dass es sicher ist, Result anzurufen (wie in den Leuten, die es geschrieben haben, hat es mit ConfigureAwait(false) gepfeffert)?

+0

übrigens .. es ist viel einfacher, nicht zu blockieren und zu verwenden, wo erforderlich zu warten, als 'ConfigureAwait' überall zu verwenden. – i3arnon

+0

@ i3arnon Wahr, aber ich schreibe eine API, die sowohl synchrone als auch asynchrone Aufrufe unterstützt. –

+1

@JamesKo: Ich empfehle, dass asynchrone Methoden asynchrone APIs verfügbar machen. Wenn Sie * jedoch * sowohl Synchronisierung als auch Async unterstützen müssen (z. B. aus Gründen der Abwärtskompatibilität), finden Sie möglicherweise meinen letzten [MSDN Artikel auf Brownfield Async] (https://msdn.microsoft.com/en-us/magazine/ mt238404.aspx) nützlich. –

Antwort

10

Task.Result wird nur in Anwesenheit von bestimmten SynchronizationContext s blockieren. In Konsolen-Apps gibt es keine, so dass Fortsetzungen auf der ThreadPool geplant sind. Genau wie sie sind, wenn Sie ConfigureAwait(false) verwenden.

In UI-Threads gibt es beispielsweise einen, der Fortsetzungen für den einzelnen UI-Thread plant. Wenn Sie synchron mit Task.Result mit dem Benutzeroberflächenthread auf einer Aufgabe warten, die nur auf dem UI-Thread ausgeführt werden kann, liegt ein Deadlock vor.

Darüber hinaus hängt ein Deadlock von der Implementierung GetByteArrayAsync ab. Sie können nur dann eine Deadlock-Operation ausführen, wenn es sich um eine asynchrone Methode handelt und die zugehörigen Wartefelder keine ConfigureAwait(false) verwenden.

Wenn Sie möchten, können Sie Stephen Cleary AsyncContext verwenden, die eine geeignete SynchronizationContext zu Ihrer Konsole App hinzugefügt, um zu testen, ob Ihr Code in UI-Anwendungen (oder ASP.Net) blockieren kann.


über HttpClient ‚s (und die meisten von .NET) Aufgabe-Rückkehr Methoden: Sie sind technisch nicht async. Sie verwenden die Schlüsselwörter async und await nicht. Sie geben einfach eine Aufgabe zurück. Normalerweise ein Wrapper über Task.Factory.FromAsync. Es ist also wahrscheinlich "sicher", sie trotzdem zu blockieren.

+1

Das ist sehr seltsam. Ich dachte, das gilt nur für async/erwarten. Es gibt hier keine Fortsetzung, daher verstehe ich nicht, was in einem anderen Thread geplant wäre. Darüber hinaus, wenn das 'Ergebnis' * nicht blockiert, wie Sie vorschlagen, was gibt die Methode zurück? Leere Arrays? –

+2

@Asad Ergebnis blockiert. Es blockiert nicht nur den einzelnen UI-Thread (da es sich nicht um eine UI-App handelt). Es blockiert den Hauptthread. – i3arnon

+1

@ i3arnon Es ist nicht klar, über welchen UI-Thread du sprichst. Mein Punkt ist, dass das 'Ergebnis' immer blockiert wird, unabhängig vom verfügbaren Synchronisationskontext. Wenn die Methode eine "Task" von etwas zurückgibt, muss diese etwas gelöst werden, bevor der Getter von "Task.Result" einen Wert an Sie zurückgibt. Ob dies in einer UI-App oder einer Konsolen-App ausgeführt wird, spielt für mich zumindest keine Rolle. –