Zum Fußzeileninhalt springen
Iron Academy Logo
Lernen Sie C#
Lernen Sie C#

Andere Kategorien

Beherrschung von C# Async und Await

Tim Corey
38m 57s

Die asynchrone Programmierung kann mit ihrer komplexen Terminologie abschreckend wirken, aber sie bietet erhebliche Vorteile, indem sie Anwendungen schneller und reaktionsschneller macht. Das Video von Tim Corey zum Thema "C# Async / Await - Machen Sie Ihre Anwendung reaktionsschneller mit asynchroner Programmierung" bietet eine praktische Anleitung zur effektiven Nutzung dieser Funktionen.

In diesem Artikel verwenden wir das ausführliche Video von Tim Corey, um diese Konzepte aufzuschlüsseln und die praktischen Anwendungen von async und await anhand klarer Beispiele zu demonstrieren. Wir werden verschiedene Szenarien abdecken, von asynchronen Ereignishandlern bis hin zum Umgang mit mehreren Aufgaben, Synchronisationskontexten, Fehlerbehandlung und den üblichen Fallstricken wie der Verwendung von asynchronen ungültigen Methoden. Am Ende dieses Artikels werden Sie ein solides Verständnis der asynchronen Programmierung haben und wissen, wie Sie diese in Ihren eigenen C#-Anwendungen implementieren können.

Einführung

Asynchrone Programmierung in C# ist für die Verbesserung der Anwendungsleistung unerlässlich, insbesondere bei der Arbeit mit E/A- oder CPU-gebundenen Operationen. Die async und await Schlüsselwörter erlauben es Entwicklern, asynchronen Code zu schreiben, der langlaufende Aufgaben ausführen kann, ohne den Haupt-Thread zu blockieren. Indem Sie eine Methode mit dem Schlüsselwort async kennzeichnen, signalisieren Sie, dass die Methode eine asynchrone Operation durchführen wird und eine Task oder Task<t> zurückgibt. Das await Schlüsselwort wird innerhalb einer asynchronen Methode verwendet, um die Ausführung anzuhalten, bis die zu wartende Aufgabe abgeschlossen ist, sodass andere Operationen gleichzeitig ausgeführt werden können.

Im Gegensatz zur synchronen Programmierung, bei der Aufgaben sequentiell ausgeführt werden und den aufrufenden Thread blockieren können, ermöglichen asynchrone Methoden die gleichzeitige Bearbeitung mehrerer Aufgaben, z. B. Hintergrundprozesse oder die Verarbeitung von Benutzereingaben, ohne dass der UI-Thread eingefroren wird. In diesem Artikel werden wir erkunden, wie async und await zusammenarbeiten, indem wir Konzepte wie asynchrone void-Methoden, Fehlerbehandlung, Kontextwechsel und Synchronisierungskontexte abdecken. Sie lernen, wie Sie mehrere asynchrone Operationen effizient verwalten und Ausnahmen behandeln, um eine reibungslose Ausführung über den aufrufenden Thread, den UI-Thread und den Abschluss der Aufgabe sicherzustellen. Dieser Ansatz ist besonders nützlich für Szenarien mit Dateisystemen, Netzwerkanfragen oder gleichzeitigen Anfragen, bei denen es auf Reaktionsfähigkeit ankommt.

Tim führt in die asynchrone Programmierung ein, indem er den Unterschied zwischen synchronen und asynchronen Operationen erklärt. Bei der synchronen Programmierung werden Aufgaben sequentiell ausgeführt, was zu Verzögerungen führt, wenn eine Aufgabe lange dauert. Bei der asynchronen Programmierung können Aufgaben parallel oder im Hintergrund ausgeführt werden, was die Leistung und Reaktionsfähigkeit verbessert.

Vorteile der asynchronen Programmierung

Tim hebt zwei Hauptvorteile der asynchronen Programmierung hervor:

  1. Verbesserte Reaktionsfähigkeit der Benutzeroberfläche: Durch die asynchrone Ausführung von Aufgaben bleibt die Benutzeroberfläche auch bei langwierigen Vorgängen reaktionsfähig.

  2. Parallele Ausführung: Unabhängige Aufgaben können parallel ausgeführt werden, wodurch sich die Gesamtzeit, die für ihre Ausführung benötigt wird, verringert.

Demo Anwendung Walk-through

Tim richtet eine Windows Presentation Foundation (WPF)-Demoanwendung ein, um den Unterschied zwischen synchronen und asynchronen Operationen zu demonstrieren. Die Anwendung verfügt über zwei Schaltflächen: eine für die synchrone Ausführung von Aufgaben und eine weitere für die asynchrone Ausführung von Aufgaben.

Demo-Anwendung

Die Anwendung enthält auch einen Ergebnisbereich, in dem die Ausgabe angezeigt wird.

Synchroner Betrieb

Tim erklärt den Code hinter der synchronen Operation. Ein Stopwatch wird verwendet, um die Ausführungszeit der Aufgabe zu messen.

private void ExecuteSync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    List<string> websites = GetWebsiteList();
    foreach (var website in websites)
    {
        string result = DownloadWebsite(website);
        ReportWebsiteInfo(website, result);
    }
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

private List<string> GetWebsiteList()
{
    return new List<string>
    {
        "https://www.yahoo.com",
        "https://www.google.com",
        "https://www.microsoft.com",
        "https://www.cnn.com",
        "https://www.codeproject.com",
        "https://www.stackoverflow.com"
    };
}

private string DownloadWebsite(string websiteURL)
{
    WebClient client = new WebClient();
    return client.DownloadString(websiteURL);
}

private void ReportWebsiteInfo(string website, string data)
{
    ResultsWindow.Text += $"{website}: {data.Length} characters{Environment.NewLine}";
}
private void ExecuteSync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    List<string> websites = GetWebsiteList();
    foreach (var website in websites)
    {
        string result = DownloadWebsite(website);
        ReportWebsiteInfo(website, result);
    }
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

private List<string> GetWebsiteList()
{
    return new List<string>
    {
        "https://www.yahoo.com",
        "https://www.google.com",
        "https://www.microsoft.com",
        "https://www.cnn.com",
        "https://www.codeproject.com",
        "https://www.stackoverflow.com"
    };
}

private string DownloadWebsite(string websiteURL)
{
    WebClient client = new WebClient();
    return client.DownloadString(websiteURL);
}

private void ReportWebsiteInfo(string website, string data)
{
    ResultsWindow.Text += $"{website}: {data.Length} characters{Environment.NewLine}";
}

Dieser Code führt die folgenden Schritte aus:

  1. Start der Stoppuhr: Misst die Ausführungszeit.
  2. Die Website-Liste abrufen: Ruft eine Liste von Website-URLs ab.
  3. Jede Website herunterladen: Lädt den Inhalt der einzelnen Websites herunter.
  4. Informationen zur Website melden: Zeigt die URL und die Länge des heruntergeladenen Inhalts an.
  5. Stop the Stopwatch: Hält den Timer an und meldet die Gesamtausführungszeit.

Überwachung der synchronen Operation

Tim führt die Anwendung aus, um den synchronen Betrieb zu demonstrieren. Er stellt fest, dass die Benutzeroberfläche nicht mehr reagiert, während die Websites heruntergeladen werden, und dass die Ergebnisse auf einmal angezeigt werden, nachdem die Downloads abgeschlossen sind.

Synchroner Vorgang

Für eine praktische Demonstration des Formulars, das sich nicht bewegt, sehen Sie sich das Video zum besseren Verständnis bei 9:20 an.

Erstellen einer asynchronen Aufgabe

Tim geht das erste Problem an, indem er die synchrone Methode in eine asynchrone Methode umwandelt. Dies beinhaltet die Verwendung der Schlüsselwörter async und await. Hier ist eine schrittweise Erklärung des Prozesses:

  1. Kopieren einer vorhandenen synchronen Methode:

    • Tim kopiert die vorhandene synchrone Methode und benennt sie um, um anzugeben, dass sie asynchron sein wird.
    private async Task RunDownloadAsync()
    {
       // Same code as RunDownloadSync, but will be modified for async
    }
    private async Task RunDownloadAsync()
    {
       // Same code as RunDownloadSync, but will be modified for async
    }
  2. Download-Aufruf für asynchrone Ausführung modifizieren:

    • Tim bettet den Download-Aufruf in ein Task.Run ein, um ihn asynchron auszuführen.
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       return await Task.Run(() => DownloadWebsite(websiteURL));
    }
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       return await Task.Run(() => DownloadWebsite(websiteURL));
    }
    • Das await Schlüsselwort stellt sicher, dass die Methode wartet, bis die asynchrone Aufgabe abgeschlossen ist, bevor sie fortfährt.
  3. Sicherstellen, dass die Methode asynchron ist:

    • Die Methodensignatur wird aktualisiert, um das async Schlüsselwort einzuschließen, und sie gibt eine Task zurück.
    private async Task RunDownloadAsync()
    {
       List<string> websites = GetWebsiteList();
       foreach (var website in websites)
       {
           var result = await DownloadWebsiteAsync(website);
           ReportWebsiteInfo(website, result);
       }
    }
    private async Task RunDownloadAsync()
    {
       List<string> websites = GetWebsiteList();
       foreach (var website in websites)
       {
           var result = await DownloadWebsiteAsync(website);
           ReportWebsiteInfo(website, result);
       }
    }
  4. Richtig mit dem Ereignis umgehen:

    • Der Ereignis-Handler für das Klicken einer Schaltfläche wird aktualisiert, um die neue asynchrone Methode aufzurufen.
    private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
    {
       Stopwatch stopwatch = Stopwatch.StartNew();
       await RunDownloadAsync();
       stopwatch.Stop();
       ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
    }
    private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
    {
       Stopwatch stopwatch = Stopwatch.StartNew();
       await RunDownloadAsync();
       stopwatch.Stop();
       ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
    }
    • Beachten Sie, dass Ereignis-Handler einen ungültigen Wert zurückgeben können, auch wenn sie asynchrone Methoden aufrufen.

Die Reaktionsfähigkeit der Benutzeroberfläche verbessern

Tim zeigt, dass durch die Verwendung des await Schlüsselwortes die Benutzeroberfläche reaktionsfähig bleibt. Dies erlaubt es Benutzern, mit dem Fenster zu interagieren, während die asynchrone Aufgabe läuft.

// UI remains responsive
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
// UI remains responsive
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

Korrektes Timing sicherstellen

Um sicherzustellen, dass die gesamte Ausführungszeit korrekt gemeldet wird, fügt Tim das await Schlüsselwort zum Aufruf im Button-Klick-Ereignishandler hinzu.

// Correctly waits for the asynchronous task to complete
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
// Correctly waits for the asynchronous task to complete
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

Durch das Warten auf den Abschluss der asynchronen Methode wird die Ausführungszeit genau gemessen, und die Ergebnisse werden angezeigt, sobald sie abgerufen werden.

Parallele Asynchronität erstellen

Tim geht auf die Einschränkung ein, dass man auf die sequentielle Ausführung jeder Aufgabe warten muss, indem er die parallele Ausführung verwendet. So ändert er den Code, um dies zu erreichen:

  1. Kopieren der vorhandenen Async-Methode:

    • Tim dupliziert die vorhandene async-Methode und benennt sie um, um auf die parallele Ausführung hinzuweisen.
    private async Task RunDownloadParallelAsync()
    {
       // Parallel execution logic will be added here
    }
    private async Task RunDownloadParallelAsync()
    {
       // Parallel execution logic will be added here
    }
  2. Erstellen Sie eine Liste von Aufgaben:

    • Es wird eine Liste mit Aufgaben erstellt, in der alle Download-Aufgaben gespeichert werden.
    List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
    List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
  3. Alle Aufgaben ohne Wartezeit einleiten:

    • Anstatt jede Download-Aufgabe sofort abzuwarten, fügt Tim sie der Liste der Aufgaben hinzu.
    foreach (var website in websites)
    {
       tasks.Add(DownloadWebsiteAsync(website));
    }
    foreach (var website in websites)
    {
       tasks.Add(DownloadWebsiteAsync(website));
    }
  4. Alle Aufgaben abwarten:

    • Die Task.WhenAll Methode wird verwendet, um das Abschließen aller Aufgaben zu erwarten. Diese Methode gibt ein Array von Ergebnissen zurück, sobald alle Aufgaben abgeschlossen sind.
    WebsiteDataModel[] results = await Task.WhenAll(tasks);
    WebsiteDataModel[] results = await Task.WhenAll(tasks);
  5. Bearbeiten der Ergebnisse:

    • Nachdem alle Aufgaben abgeschlossen sind, verarbeitet Tim die Ergebnisse in einer Schleife.
    foreach (var result in results)
    {
       ReportWebsiteInfo(result);
    }
    foreach (var result in results)
    {
       ReportWebsiteInfo(result);
    }

Hier ist der vollständige Code für die parallele Ausführungsmethode:

private async Task RunDownloadParallelAsync()
{
    List<string> websites = GetWebsiteList();
    List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();

    foreach (var website in websites)
    {
        tasks.Add(DownloadWebsiteAsync(website));
    }

    WebsiteDataModel[] results = await Task.WhenAll(tasks);

    foreach (var result in results)
    {
        ReportWebsiteInfo(result);
    }
}
private async Task RunDownloadParallelAsync()
{
    List<string> websites = GetWebsiteList();
    List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();

    foreach (var website in websites)
    {
        tasks.Add(DownloadWebsiteAsync(website));
    }

    WebsiteDataModel[] results = await Task.WhenAll(tasks);

    foreach (var result in results)
    {
        ReportWebsiteInfo(result);
    }
}

Aktualisierung des Event-Handlers

Tim aktualisiert den Ereignis-Handler für den Schaltflächenklick, um die neue parallele Ausführungsmethode aufzurufen.

private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadParallelAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadParallelAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

Durch das Warten auf RunDownloadParallelAsync wird die gesamte Ausführungszeit genau gemessen und gemeldet.

Beobachtung der Leistungsverbesserung

Tim demonstriert die Leistungsverbesserung, indem er die Anwendung mit paralleler Ausführung ausführt. Die Ergebnisse zeigen eine erhebliche Reduzierung der Gesamtausführungszeit im Vergleich zur sequenziellen Ausführung.

// Parallel execution
async Task RunDownloadParallelAsync();
// Parallel execution
async Task RunDownloadParallelAsync();

Der Geschwindigkeitszuwachs ist offensichtlich, da die Websites gleichzeitig heruntergeladen werden, wodurch die Gesamtwartezeit auf die Dauer des langsamsten einzelnen Downloads reduziert wird.

Methoden in Task.Run() einbetten vs. asynchroner Methodenaufruf

Tim erklärt das Konzept der Verwendung von Task.Run(), um eine synchrone Methode einzubetten und sie asynchron zu machen, wenn Sie die Originalmethode nicht ändern können. Er zeigt aber auch den bevorzugten Ansatz, die Methode selbst so zu modifizieren, dass sie asynchron ist, wenn man die Kontrolle darüber hat.

  1. Methode in Task.Run() einbetten:

    • Dieser Ansatz ist nützlich, wenn Sie den Code der Originalmethode nicht ändern können, sie aber dennoch asynchron ausführen möchten.
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       return await Task.Run(() => DownloadWebsite(websiteURL));
    }
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       return await Task.Run(() => DownloadWebsite(websiteURL));
    }
  2. Eine Methode asynchron machen:

    • Wenn Sie die Methode ändern können, ist es besser, die Methode selbst zu konvertieren, um asynchron zu sein, indem Sie eine geeignete asynchrone API verwenden.

    • Tim demonstriert dies, indem er DownloadWebsite zu DownloadWebsiteAsync ändert und DownloadStringTaskAsync aus WebClient verwendet.
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       WebClient client = new WebClient();
       string data = await client.DownloadStringTaskAsync(websiteURL);
    
       return new WebsiteDataModel { URL = websiteURL, Data = data };
    }
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       WebClient client = new WebClient();
       string data = await client.DownloadStringTaskAsync(websiteURL);
    
       return new WebsiteDataModel { URL = websiteURL, Data = data };
    }
  3. Anpassung der Aufrufmethode:

    • Nach der Umwandlung der Methode muss der aufrufende Code angepasst werden, um den Task.Run() Wrapper zu entfernen und die asynchrone Methode direkt aufzurufen.
    private async Task RunDownloadParallelAsync()
    {
       List<string> websites = GetWebsiteList();
       List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
    
       foreach (var website in websites)
       {
           tasks.Add(DownloadWebsiteAsync(website));
       }
    
       WebsiteDataModel[] results = await Task.WhenAll(tasks);
    
       foreach (var result in results)
       {
           ReportWebsiteInfo(result);
       }
    }
    private async Task RunDownloadParallelAsync()
    {
       List<string> websites = GetWebsiteList();
       List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
    
       foreach (var website in websites)
       {
           tasks.Add(DownloadWebsiteAsync(website));
       }
    
       WebsiteDataModel[] results = await Task.WhenAll(tasks);
    
       foreach (var result in results)
       {
           ReportWebsiteInfo(result);
       }
    }

Zusammenfassung der wichtigsten Punkte

Abschließend gibt Tim noch einige wichtige Hinweise zur Verwendung von async und await:

  • Sicherstellen, dass Methoden Aufgaben zurückgeben: Wann immer Sie eine Methode als async kennzeichnen, sollte sie eine Task oder Task<t> anstelle von void zurückgeben (außer für Ereignishandler).

  • Verwenden Sie await für zuverlässige Operationen: Verwenden Sie das await Schlüsselwort, wenn Sie das Ergebnis einer asynchronen Operation benötigen, bevor Sie fortfahren.

  • Aufgaben-Wrapper für nicht änderbaren Code: Verwenden Sie Task.Run(), um synchrone Methoden einzubetten, wenn Sie die Originalmethode nicht ändern können.

  • Asynchrone Methoden angemessen kennzeichnen: Fügen Sie immer Async zum Methodennamen hinzu, um anzuzeigen, dass es sich um eine asynchrone Operation handelt.

  • Parallele vs. Sequentielle Ausführung: Entscheiden Sie anhand der Abhängigkeiten zwischen den Aufgaben, ob diese parallel ausgeführt werden können oder aufeinander warten sollen.

Tim betont, dass die asynchrone Programmierung in C# durch async, await und Task vereinfacht wurde. Komplexe Themen wie Threading-Kontexte und Apartment-Modelle werden im Hintergrund behandelt.

Abschluss

Tim Coreys Tutorial zu async und await in C# bietet eine unschätzbare Ressource für die Beherrschung der asynchronen Programmierung. Durch die sorgfältige Erläuterung von Konzepten wie Task-Parallelität, Reaktionsfähigkeit der Benutzeroberfläche und die Bedeutung der Verwendung von async und await im richtigen Kontext gibt Tim den Entwicklern die Werkzeuge an die Hand, um schnellere und reaktionsfähigere Anwendungen zu erstellen. Sein detailliertes Walkthrough zeigt, wie man effizient mit lang laufenden Aufgaben umgeht, ohne den Haupt-Thread zu blockieren, sowie Strategien zur Verwaltung asynchroner Operationen und paralleler Ausführung.

Für ein tieferes Verständnis und zusätzliche praktische Beispiele empfehle ich das Video von Tim Corey. Seine Einblicke in async void-Methoden, Fehlerbehandlung und die Verwaltung von Synchronisationskontexten werden Ihre Fähigkeit zur Implementierung dieser Konzepte in Ihren Projekten weiter verbessern.

Hero Worlddot related to Beherrschung von C# Async und Await
Hero Affiliate related to Beherrschung von C# Async und Await

Verdienen Sie mehr, indem Sie teilen, was Sie lieben

Erstellen Sie Inhalte für Entwickler, die mit .NET, C#, Java, Python oder Node.js arbeiten? Verwandeln Sie Ihr Fachwissen in ein zusätzliches Einkommen!

Iron Support Team

Wir sind 24 Stunden am Tag, 5 Tage die Woche online.
Chat
E-Mail
Rufen Sie mich an