Warten gegen Task.Warten - Deadlock?

194

Ich verstehe den Unterschied zwischen Task.Waitund nicht ganz await.

Ich habe etwas Ähnliches wie die folgenden Funktionen in einem ASP.NET-WebAPI-Dienst:

public class TestController : ApiController
{
    public static async Task<string> Foo()
    {
        await Task.Delay(1).ConfigureAwait(false);
        return "";
    }

    public async static Task<string> Bar()
    {
        return await Foo();
    }

    public async static Task<string> Ros()
    {
        return await Bar();
    }

    // GET api/test
    public IEnumerable<string> Get()
    {
        Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());

        return new string[] { "value1", "value2" }; // This will never execute
    }
}

Wo Getwird Deadlock.

Was könnte das verursachen? Warum verursacht dies kein Problem, wenn ich eine blockierende Wartezeit anstelle von verwende await Task.Delay?

Ronag
quelle
@Servy: Ich werde mit einem Repo zurückkommen, sobald ich Zeit habe. Im Moment funktioniert es mit Task.Delay(1).Wait()dem ist gut genug.
Ronag
2
Task.Delay(1).Wait()ist im Grunde das gleiche wie Thread.Sleep(1000). Im tatsächlichen Produktionscode ist dies selten angemessen.
Servy
@ronag: Du WaitAllverursachst den Deadlock. Weitere Informationen finden Sie unter dem Link zu meinem Blog in meiner Antwort. Sie sollten await Task.WhenAllstattdessen verwenden.
Stephen Cleary
6
@ronag Da Sie ConfigureAwait(false)einen einzelnen Anruf zum Deadlock haben Baroder Rosnicht blockieren werden, aber weil Sie eine Aufzählung haben, die mehr als einen erstellt und dann auf all diese wartet, blockiert der erste Balken den zweiten. Wenn Sie await Task.WhenAllnicht auf alle Aufgaben warten, um den ASP-Kontext nicht zu blockieren, wird die Methode normal zurückgegeben.
Servy
2
@ronag Ihre andere Option wäre, den .ConfigureAwait(false) gesamten Baum bis zum Blockieren hinzuzufügen. Auf diese Weise versucht nie etwas, zum Hauptkontext zurückzukehren. das würde funktionieren. Eine andere Möglichkeit wäre, einen inneren Synchronisationskontext aufzurufen. Link . Wenn Sie das Task.WhenAllin ein setzen AsyncPump.Run, wird es das Ganze effektiv blockieren, ohne dass Sie ConfigureAwaitirgendwo hin müssen, aber das ist wahrscheinlich eine zu komplexe Lösung.
Servy

Antworten:

268

Waitund await- obwohl konzeptionell ähnlich - tatsächlich völlig unterschiedlich sind.

Waitwird synchron blockiert, bis die Aufgabe abgeschlossen ist. Der aktuelle Thread wird also buchstäblich blockiert, bis die Aufgabe abgeschlossen ist. In der Regel sollten Sie " asyncganz nach unten" verwenden. Blockieren Sie also keinen asyncCode. In meinem Blog gehe ich auf die Details ein, wie das Blockieren von asynchronem Code zu einem Deadlock führt .

awaitwartet asynchron, bis die Aufgabe abgeschlossen ist. Dies bedeutet, dass die aktuelle Methode "angehalten" ist (ihr Status wird erfasst) und die Methode eine unvollständige Aufgabe an ihren Aufrufer zurückgibt. Später, wenn der awaitAusdruck abgeschlossen ist, wird der Rest der Methode als Fortsetzung geplant.

Sie haben auch einen "kooperativen Block" erwähnt, mit dem Sie vermutlich eine Aufgabe meinen, die Sie Waitim wartenden Thread ausführen können. Es gibt Situationen, in denen dies passieren kann, aber es ist eine Optimierung. Es gibt viele Situationen, in denen dies nicht möglich ist, z. B. wenn die Aufgabe für einen anderen Scheduler bestimmt ist oder wenn sie bereits gestartet wurde oder wenn es sich um eine Nicht-Code-Aufgabe handelt (wie in Ihrem Codebeispiel: WaitDie DelayAufgabe kann nicht inline ausgeführt werden, da kein Code vorhanden ist dafür).

Sie können mein async/ awaitIntro hilfreich finden.

Stephen Cleary
quelle
4
Ich denke, es gibt ein Missverständnis, Waitfunktioniert gut awaitDeadlocks.
Ronag
5
Nein, der Taskplaner wird das nicht tun. Waitblockiert den Thread und kann nicht für andere Zwecke verwendet werden.
Stephen Cleary
8
@ronag Ich vermute, Sie haben gerade Ihre Methodennamen verwechselt und Ihr Deadlock wurde tatsächlich mit dem Blockierungscode verursacht und hat mit dem awaitCode gearbeitet. Entweder das, oder der Deadlock hatte nichts damit zu tun, und Sie haben das Problem falsch diagnostiziert.
Servy
3
@hexterminator: Dies ist beabsichtigt - es funktioniert hervorragend für UI-Apps, steht jedoch ASP.NET-Apps eher im Weg. ASP.NET Core hat dies behoben, indem das SynchronizationContextBlockieren innerhalb einer ASP.NET Core-Anforderung nicht mehr blockiert wurde.
Stephen Cleary
1
Auf msdn heißt es hier, dass wait asynchron auf einem separaten Thread ausgeführt wird. Vermisse ich etwas msdn.microsoft.com/en-us/library/hh195051(v=vs.110).aspx
batmaci
6

Basierend auf dem, was ich aus verschiedenen Quellen gelesen habe:

Ein awaitAusdruck blockiert nicht den Thread, auf dem er ausgeführt wird. Stattdessen veranlasst es den Compiler, den Rest der asyncMethode als Fortsetzung der erwarteten Aufgabe anzumelden. Die Steuerung kehrt dann zum Aufrufer der asyncMethode zurück. Wenn die Aufgabe abgeschlossen ist, ruft sie ihre Fortsetzung auf und die Ausführung der asyncMethode wird dort fortgesetzt, wo sie aufgehört hat.

Um auf den Abschluss eines einzelnen taskzu warten , können Sie dessen Task.WaitMethode aufrufen . Ein Aufruf der WaitMethode blockiert den aufrufenden Thread, bis die Ausführung der einzelnen Klasseninstanz abgeschlossen ist. Die parameterlose Wait()Methode wird verwendet, um bedingungslos zu warten, bis eine Aufgabe abgeschlossen ist. Die Aufgabe simuliert die Arbeit, indem die Thread.SleepMethode zwei Sekunden lang in den Ruhezustand versetzt wird.

Dieser Artikel ist auch eine gute Lektüre.

Ayushmati
quelle
3
"Ist das dann nicht technisch falsch? Kann jemand bitte klarstellen?" - kann ich klarstellen; stellst du das als frage (Ich möchte nur klarstellen, ob Sie fragen oder antworten). Wenn Sie fragen: Es kann besser als separate Frage funktionieren; Es ist unwahrscheinlich, dass hier neue Antworten als Antwort
gesammelt werden
1
Ich habe die Frage beantwortet und eine separate Frage für den Zweifel gestellt, den ich hier hatte. Stackoverflow.com/questions/53654006/… Danke @MarcGravell. Können Sie bitte Ihre Löschabstimmung für die Antwort jetzt entfernen?
Ayushmati
"Können Sie bitte Ihre Löschabstimmung für die Antwort jetzt entfernen?" - das ist nicht meins; Dank der ♦ wäre eine solche Abstimmung von mir sofort wirksam geworden. Ich glaube jedoch nicht, dass dies die wichtigsten Punkte der Frage beantwortet, bei der es um das Deadlock-Verhalten geht.
Marc Gravell
-2

Einige wichtige Fakten wurden in anderen Antworten nicht angegeben:

"async await" ist auf CIL-Ebene komplexer und kostet daher Speicher und CPU-Zeit.

Jede Aufgabe kann abgebrochen werden, wenn die Wartezeit nicht akzeptabel ist.

Für den Fall "async await" haben wir keinen Handler für eine solche Aufgabe, um sie abzubrechen oder zu überwachen.

Die Verwendung von Task ist flexibler als "asynchrones Warten".

Jede Synchronisierungsfunktion kann asynchron umbrochen werden.

public async Task<ActionResult> DoAsync(long id) 
{ 
    return await Task.Run(() => { return DoSync(id); } ); 
} 

Ich verstehe nicht, warum ich mit der Codeduplizierung für die Synchronisierungs- und Asynchronisierungsmethode oder mit Hacks leben muss.

user1785960
quelle