Frage

Ich lerne immer noch async/await, also bitte entschuldigen Sie, wenn ich etwas Offensichtliches frage.Betrachten Sie das folgende Beispiel:

class Program  {

    static void Main(string[] args) {
        var result = FooAsync().Result;
        Console.WriteLine(result);
    }

    static async Task<int> FooAsync() {

        var t1 = Method1Async();
        var t2 = Method2Async();

        var result1 = await t1;
        var result2 = await t2;

        return result1 + result2;

    }

    static Task<int> Method1Async() {
        return Task.Run(
            () => {
                Thread.Sleep(1000);
                return 11;
            }
        );
    }

    static Task<int> Method2Async() {
        return Task.Run(
            () => {
                Thread.Sleep(1000);
                return 22;
            }
        );
    }

}

Dies verhält sich wie erwartet und gibt „33“ in der Konsole aus.

Wenn ich das ersetze zweite await mit explizitem Warten...

static async Task<int> FooAsync() {

    var t1 = Method1Async();
    var t2 = Method2Async();

    var result1 = await t1;
    var result2 = t2.Result;

    return result1 + result2;

}

...Ich scheine das gleiche Verhalten zu haben.

Sind diese beiden Beispiele völlig gleichwertig?

Und wenn sie in diesem Fall gleichwertig sind, gibt es andere Fälle, in denen die letzten ersetzt werden? await durch ein explizites Warten einen Unterschied machen würde?

War es hilfreich?

Lösung 3

OK, ich glaube, ich habe es herausgefunden, also möchte ich es zusammenfassen, hoffentlich mit einer ausführlicheren Erklärung als die bisher gegebenen Antworten ...

Kurze Antwort

Den zweiten ersetzen await mit einer expliziten Wartezeit hat keine nennenswerten Auswirkungen auf eine Konsolenanwendung, blockiert jedoch den UI-Thread einer WPF- oder WinForms-Anwendung für die Dauer der Wartezeit.

Außerdem ist die Ausnahmebehandlung etwas anders (wie von Stephen Cleary festgestellt).

Lange Antwort

Kurz gesagt, die await macht dies:

  1. Wenn die erwartete Aufgabe bereits abgeschlossen ist, ruft sie einfach ihr Ergebnis ab und fährt fort.
  2. Wenn nicht, dann Beiträge die Fortsetzung (der Rest der Methode nach dem await) zum aktuell Synchronisationskontext, falls vorhanden.Im Wesentlichen, await versucht uns dorthin zurückzubringen, wo wir angefangen haben.
    • Wenn kein aktueller Kontext vorhanden ist, wird einfach das Original verwendet TaskScheduler, bei dem es sich normalerweise um einen Thread-Pool handelt.

Der zweite (und der dritte usw.) await macht das Gleiche.

Da die Konsolenanwendungen normalerweise keinen Synchronisierungskontext haben, werden Fortsetzungen normalerweise vom Thread-Pool verarbeitet, sodass es kein Problem gibt, wenn wir innerhalb der Fortsetzung blockieren.

WinForms oder WPF hingegen verfügen über einen Synchronisierungskontext, der zusätzlich zu ihrer Nachrichtenschleife implementiert ist.Daher, await Wenn ein Befehl, der auf einem UI-Thread ausgeführt wird, (irgendwann) seine Fortsetzung auch auf dem UI-Thread ausführt.Wenn wir die Fortsetzung blockieren, wird die Nachrichtenschleife blockiert und die Benutzeroberfläche reagiert nicht mehr, bis wir die Blockierung aufheben.OTOH, wenn wir nur await, werden Fortsetzungen sauber gepostet, um schließlich im UI-Thread ausgeführt zu werden, ohne jemals den UI-Thread zu blockieren.

Im folgenden WinForms-Formular, das eine Schaltfläche und eine Beschriftung enthält, wird verwendet await sorgt dafür, dass die Benutzeroberfläche jederzeit reagiert (beachten Sie die async vor dem Click-Handler):

public partial class Form1 : Form {

    public Form1() {
        InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e) {
        var result = await FooAsync();
        label1.Text = result.ToString();
    }

    static async Task<int> FooAsync() {

        var t1 = Method1Async();
        var t2 = Method2Async();

        var result1 = await t1;
        var result2 = await t2;

        return result1 + result2;

    }

    static Task<int> Method1Async() {
        return Task.Run(
            () => {
                Thread.Sleep(3000);
                return 11;
            }
        );
    }

    static Task<int> Method2Async() {
        return Task.Run(
            () => {
                Thread.Sleep(5000);
                return 22;
            }
        );
    }

}

Wenn wir den zweiten ersetzen await In FooAsync mit t2.Result, würde es nach dem Klicken auf die Schaltfläche noch etwa 3 Sekunden lang reagieren und dann etwa 2 Sekunden lang einfrieren:

  • Die Fortsetzung nach dem ersten await wird höflich warten, bis er an der Reihe ist, bis er im UI-Thread eingeplant wird, was danach passieren würde Method1Async() Aufgabe beendet, d.h.nach etwa 3 sekunden,
  • an welchem ​​Punkt die t2.Result wird den UI-Thread grob blockieren, bis der Method2Async() Die Aufgabe wird etwa 2 Sekunden später beendet.

Wenn wir das entfernt haben async vor dem button1_Click und ersetzte es await mit FooAsync().Result es würde zum Stillstand kommen:

  • Der UI-Thread würde warten FooAsync() Aufgabe zu erledigen,
  • das darauf warten würde, dass es weitergeht,
  • die darauf warten würde, dass der UI-Thread verfügbar wird,
  • was nicht der Fall ist, da es durch die blockiert wird FooAsync().Result.

Der Artikel „Await-, SynchronizationContext- und Konsolen-Apps“ von Stephen Toub war für mich von unschätzbarem Wert für das Verständnis dieses Themas.

Andere Tipps

Ihre Ersatzversion blockiert den aufrufenden Thread, der auf die Aufgabe wartet, um zu beenden.Es ist schwer, einen sichtbaren Unterschied in einer Console-App so zu sehen, da Sie absichtlich in der Main blockieren, aber sie sind definitiv nicht gleichwertig.

Sie sind nicht gleichwertig.

Task.Result Blöcke, bis das Ergebnis verfügbar ist.Wie ich auf meinem Blog erkläre, ist dies der Fall kann zu Deadlocks führen wenn Sie eine haben async Kontext, der exklusiven Zugriff erfordert (z. B. eine Benutzeroberfläche oder eine ASP.NET-App).

Auch, Task.Result wird alle Ausnahmen einschließen AggregateException, Daher ist die Fehlerbehandlung schwieriger, wenn Sie synchron blockieren.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top