Warum blockiert Task.WaitAll () hier nicht oder verursacht einen Deadlock?

8

Im folgenden Beispiel werden zwei Aufrufe await verwendet. Um Leistung zu erhalten, wird das Sample stattdessen Task.WaitAll() konvertiert (nicht wirklich schneller, aber dies ist nur ein Beispiel).

Dies ist Code aus einer Bibliothek mit Sqlite.Net auf Android und die Methode wird von OnResume() im Haupt-UI-Thread aufgerufen:

%Vor%

Hier ist die Alternative:

%Vor%

Aber nach meinem Verständnis sollte Task.WaitAll() den UI-Thread während des Wartens blockieren, was zu einem Deadlock führt. Aber es funktioniert gut. Ist das so, weil die beiden Aufrufe tatsächlich nichts auf dem UI-Thread aufrufen?

Was ist der Unterschied, wenn ich stattdessen Task.WhenAll() verwende? Ich denke, es würde funktionieren, selbst wenn der UI-Thread aufgerufen würde, genau wie bei await .

    
Krumelur 27.03.2014, 20:58
quelle

3 Antworten

13

Ich beschreibe die Details zur Deadlock-Situation in meinem Blog . Ich habe auch einen MSDN-Artikel zu SynchronizationContext , den Sie möglicherweise hilfreich finden.

Zusammenfassend wird Task.WaitAll in Ihrem Szenario festgefahren, aber nur dann, wenn die Aufgaben zum Abschluss mit dem UI-Thread synchronisiert werden müssen. Sie können daraus schließen, dass CreateTableAsync<T>() nicht mit dem UI-Thread synchronisiert wird.

Im Gegensatz dazu blockiert dieser Code:

%Vor%

Ich empfehle Ihnen, nicht auf asynchronem Code zu blockieren; In der async -Welt ist die Synchronisierung mit dem Kontext das Standard -Verhalten (wie ich in meinem async intro ), so dass es leicht ist, es versehentlich zu tun. Einige Änderungen an Sqlite.Net werden in der Zukunft (versehentlich) wieder mit dem ursprünglichen Kontext synchronisiert, und dann wird jeder Code, der Task.WaitAll wie das ursprüngliche Beispiel verwendet, plötzlich festgefahren.

Am besten verwenden Sie async "den ganzen Weg":

%Vor%

"Async den ganzen Weg" ist eine der Richtlinien, die ich in meinem asynchronen Best Practices-Artikel .

    
Stephen Cleary 27.03.2014, 23:15
quelle
0

Wenn Sie den UI-Thread (und den aktuellen Synchronisationskontext) blockieren, wird nur ein Deadlock verursacht, wenn einer der wartenden Tasks einen Delegaten zum aktuellen Kontext marschiert und dann darauf wartet (synchron oder asynchron). Das synchrone Blockieren auf jeder asynchronen Methode ist in keinem einzigen Fall ein sofortiger Deadlock.

Weil async -Methoden standardmäßig den Rest der Methode zum aktuellen Synchronisationskontext marschieren und nach jedem einzelnen await , und weil die Aufgabe niemals enden wird, bedeutet das, dass sie synchron auf Methoden wartet dass async/await verwendet wird oft Deadlock; zumindest solange das beschriebene Verhalten nicht explizit übersteuert wird (durch, sagen wir ConfigureAwait(false) ).

Die Verwendung von WhenAll bedeutet, dass Sie den aktuellen Synchronisationskontext nicht blockieren. Anstatt den Thread zu blockieren, planen Sie nur eine weitere Fortsetzung, die ausgeführt wird, wenn alle anderen Aufgaben abgeschlossen sind, und der Kontext frei bleibt, um alle anderen Anforderungen zu bearbeiten, die gerade jetzt sind (wie z Fortsetzung von der zugrunde liegenden Methode async , auf die WhenAll wartet).

    
Servy 27.03.2014 21:04
quelle
-1

Vielleicht wird dieses Beispiel zeigen, was passieren könnte. Es ist ein Laden von iOS-Ansichten. Probieren Sie es sowohl mit dem Anruf warten und ohne es (auskommentiert unten). Ohne Wartezeit in der Funktion wird es synchron laufen und die Benutzeroberfläche wird blockiert.

%Vor%     
SKall 27.03.2014 23:40
quelle