2017-06-29 1 views
4

ich ein einfaches WebAPI Projekt mit einem einzigen Controller und einer einzigen Methode erstellt:Warum tritt im Kontext von ASP.NET Task.Run (...) nicht auf, wenn beim Aufrufen einer asynchronen Methode ein Deadlock auftritt?

public static class DoIt 
{ 
    public static async Task<string> GetStrAsync(Uri uri) 
    { 
     using (var client = new HttpClient()) 
     { 
      var str = await client.GetStringAsync(uri); 
      return str; 
     } 
    } 
} 

public class TaskRunResultController : ApiController 
{ 
    public string Get() 
    { 
     var task = Task.Run(() => 
      DoIt.GetStrAsync(new Uri("http://google.com")) 
     ); 
     var result = task.Result; 

     return result; 
    } 
} 

ich ein gutes Verständnis von Asynchron haben/abwarten und Aufgaben; fast religiös folgt Stephen Cleary. Nur die Existenz von .Result macht mich ängstlich, und ich erwarte, dass dies zum Stillstand kommt. Ich verstehe, dass die Task.Run(...) verschwenderisch ist, verursacht, dass ein Thread belegt wird, während auf die asynchrone DoIt() fertig zu beenden.

Das Problem ist das ist nicht Deadlocking, und es gibt mir Herzklopfen. Ich sehe einige Antworten wie https://stackoverflow.com/a/32607091/1801382, und ich habe auch beobachtet, dass SynchronizationContext.Current null ist, wenn das Lambda ausgeführt wird. Allerdings gibt es ähnliche Fragen zu meinen, warum der obige Code Deadlock ist, und ich habe gesehen, Deadlocks in Fällen auftreten, in denen ConfigureAwait(false) verwendet wird (nicht erfassen den Kontext) in Verbindung mit .Result.

Was gibt?

Antwort

5

Result allein wird nicht zu einem Deadlock führen. Ein Deadlock ist, wenn zwei Teile des Codes beide aufeinander warten. Eine Result ist nur eine Wartezeit, so dass es Teil eines Deadlocks sein kann, aber es immer nicht unbedingt einen Deadlock verursachen.

Im standard example die Result wartet auf die Aufgabe abzuschließen, während auf den Kontext zu halten, aber die Aufgabe kann nicht abgeschlossen werden, weil es Warten ist für den Kontext frei zu sein. Es gibt also einen Stillstand - sie warten auf einander.

ASP.NET Core does not have a context at all, so Result wird dort nicht blockieren. (Es ist immer noch keine gute Idee aus anderen Gründen, aber es wird nicht Deadlock).

Das Problem ist dies nicht Deadlocks, und es gibt mir Herzklopfen.

Dies liegt daran, dass für die Aufgabe Task.Run der Kontext nicht erforderlich ist. Es ist also sicher, Task.Run(...).Result anzurufen. Die Result wartet auf den Abschluss der Aufgabe und hält den Kontext fest (wodurch verhindert wird, dass andere Teile der Anforderung in diesem Kontext ausgeführt werden), aber das ist in Ordnung, da die Task Task.Run den Kontext überhaupt nicht benötigt.

Task.Run ist immer noch keine gute Idee auf ASP.NET (aus anderen Gründen), aber dies ist eine vollkommen gültige Technik, die von Zeit zu Zeit nützlich ist. Es wird nicht immer blockieren, da die Task.Run Aufgabe den Kontext nicht benötigt.

Allerdings gibt es ähnliche Fragen Mine zu fragen, warum der obige Code nicht Deadlock,

ähnlich, aber nicht genau. Sehen Sie sich die Code-Anweisungen in diesen Fragen genau an und fragen Sie sich, in welchem ​​Kontext sie laufen.

und ich habe auftreten gesehen Deadlocks in Fällen, in denen ConfigureAwait (false) verwendet wird (nicht den Kontext erfassen) in Verbindung mit .RESULT.

Die Result/Kontext Deadlock ist eine sehr häufige - wahrscheinlich die häufigste. Aber es ist nicht das einzige Deadlock-Szenario da draußen.

Here's an example einen viel schwieriger Deadlock, die auftauchen können, wenn Sie innerhalbasync Code sperren (ohne einen Kontext). Diese Art von Szenario ist jedoch viel seltener.

+0

Danke, Stephen. Ich vermutete, dass die Antwort im Wesentlichen "das ist, weil die Aufgabe Task.Run den Kontext nicht erfordert", aber zögerte, dies "sicher" zu nennen. Ich verstehe die anderen Auswirkungen. Danke für das Blockierungsbeispiel; wie es jetzt 2017 ist, ist das seltenere Szenario immer noch gültig? Sie geben auch an, dass dies in GUI- oder ASP.NET-Code nicht vorkommen wird - in welchem ​​Szenario _dies_ es Deadlock? – aholmes

+0

Ja. 'await' verwendet immer noch' ExecuteSynchronously', sodass das seltenere Szenario immer noch gültig ist. In diesem Fall ist es ein Deadlock, bei dem zwei Thread-Pool-Threads auf einander warten. –

0

Ich gebe Ihnen als kurze Antwort umgekehrt: wie eine Sackgasse produzieren:

Lets in einem Click Handler starten, die synchron ausgeführt wird und einigen asynchronen Aufruf zu einem separaten Task-Offloading, dann warten das Ergebnis

private void MenuItem_Click(object sender, RoutedEventArgs e) 
{ 
    var t = Task.Run(() => DeadlockProducer(sender as MenuItem)); 
    var result = t.Result; 
} 

private async Task<int> DeadlockProducer(MenuItem sender) 
{ 
    await Task.Delay(1); 
    Dispatcher.Invoke(() => sender.Header = "Life sucks"); 
    return 0; 
} 

die Asynchron-Methode eine weitere schlechte Sache tut: es wird versucht die UI Änderung synchron zu bringen, aber das UI-Thread wartet immer noch auf die Aufgabe Ergebnis ...

Also im Grunde Task.Run ist halb heraus und wird für eine Menge gut geformten asynchronen Code funktionieren, aber es gibt Möglichkeiten, es zu brechen, also ist es keine zuverlässige Lösung.

Dieser Beispielcode wurde im Kontext von WPF geschrieben, aber ich denke ASP.Net könnte missbraucht werden, um ein ähnliches Verhalten zu erzeugen.

Verwandte Themen