Asynchrone Programmierung in C# – Grundlagen (async/await)
Lernen Sie async und await in C#, um reaktionsfähige Anwendungen mit asynchronen Tasks und Beispielen zu entwickeln.
In modernen Anwendungen ist asynchrone Programmierung wichtig,
um die Benutzeroberfläche reaktionsfähig zu halten und lang andauernde Prozesse auszuführen,
ohne die Anwendung einzufrieren.
In C# werden hierfür die Schlüsselwörter async und await verwendet.
Diese Struktur ermöglicht es, langwierige Aufgaben im Hintergrund auszuführen, während die Anwendung weiterhin reagiert.
Was ist asynchrone Programmierung?
Asynchrone Programmierung ermöglicht es, dass andere Operationen fortgesetzt werden, während auf den Abschluss einer Aufgabe gewartet wird. Sie wird besonders für I/O-basierte Aufgaben wie Dateizugriffe, Netzwerkoperationen, Datenbankabfragen oder API-Aufrufe verwendet.
// Synchron: Operationen werden nacheinander ausgeführt
DateiLesen();
DatenSenden();
Console.WriteLine("Fertig!");
// Asynchron: Operationen werden ohne Warten fortgesetzt
await DateiLesenAsync();
await DatenSendenAsync();
Console.WriteLine("Fertig!");
Die Schlüsselwörter async und await
Das Schlüsselwort async kennzeichnet eine Methode als asynchron.
Innerhalb dieser Methode wird await verwendet, um auf den Abschluss anderer asynchroner Aufgaben zu warten.
Dadurch kann der Thread weiterarbeiten, ohne blockiert zu werden.
async Task OperationAusführenAsync()
{
Console.WriteLine("Operation gestartet...");
await Task.Delay(2000); // Wartet 2 Sekunden, blockiert aber den Thread nicht
Console.WriteLine("Operation abgeschlossen!");
}
Im obigen Beispiel friert die Anwendung trotz Task.Delay nicht ein,
da die Aufgabe im Hintergrund wartet, ohne die CPU zu blockieren.
Task und Task<T> Typen
Asynchrone Methoden geben normalerweise Task oder Task<T> zurück.
Task signalisiert nur den Abschluss der Aufgabe,
während Task<T> ein Ergebnis zurückgeben kann.
// Asynchrone Methode ohne Rückgabewert
async Task DateiSpeichernAsync(string name)
{
await File.WriteAllTextAsync(name, "Dateiinhalt...");
}
// Asynchrone Methode mit Rückgabewert
async Task<string> DatenAbrufenAsync()
{
await Task.Delay(1000);
return "Daten vom Server empfangen";
}
// Verwendung:
string result = await DatenAbrufenAsync();
Console.WriteLine(result);
Asynchrone Main-Methode
Seit C# 7.1 kann auch die Main-Methode asynchron sein.
Dadurch kann await direkt in Konsolenanwendungen verwendet werden.
class Program
{
static async Task Main()
{
Console.WriteLine("Daten werden abgerufen...");
string daten = await DatenAbrufenAsync();
Console.WriteLine($"Ergebnis: {daten}");
}
static async Task<string> DatenAbrufenAsync()
{
await Task.Delay(1500);
return "Abgeschlossen!";
}
}
Wichtige Hinweise zur Verwendung von await
- await kann nur in Methoden verwendet werden, die mit
asyncmarkiert sind. - Mehrere asynchrone Aufgaben können gleichzeitig gestartet werden (
Task.WhenAll()oderTask.WhenAny()). async voidsollte nur in Ereignishandlern (Event-Handlern) verwendet werden.
// Mehrere Aufgaben parallel ausführen
var aufgabe1 = DateiLesenAsync();
var aufgabe2 = DatenSendenAsync();
await Task.WhenAll(aufgabe1, aufgabe2);
Console.WriteLine("Beide Aufgaben wurden abgeschlossen.");
Fehlerbehandlung im asynchronen Code
In asynchronen Methoden wird die Fehlerbehandlung mit try-catch-Blöcken durchgeführt.
Wenn eine mittels await abgewartete Aufgabe einen Fehler auslöst,
wird der catch-Block ausgeführt.
try
{
await DateiLesenAsync();
}
catch (IOException ex)
{
Console.WriteLine($"Fehler beim Lesen der Datei: {ex.Message}");
}
Beispiel: Daten von einer API abrufen
Im folgenden Beispiel wird eine asynchrone HTTP-Anfrage mit HttpClient durchgeführt.
Das Schlüsselwort await verhindert, dass die Anwendung während des Wartens auf die Netzwerkantwort einfriert.
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
using HttpClient client = new HttpClient();
string url = "https://jsonplaceholder.typicode.com/posts/1";
Console.WriteLine("Daten werden angefordert...");
string json = await client.GetStringAsync(url);
Console.WriteLine("Empfangenes JSON:");
Console.WriteLine(json);
}
}
WPF-Beispiel: Asynchrone Operation und UI-Aktualisierung
Im folgenden Beispiel wird in einer WPF-Anwendung beim Klicken auf eine Schaltfläche
eine lang andauernde Aufgabe (mit Task.Delay simuliert) asynchron ausgeführt.
Dank des await-Schlüsselworts bleibt die Benutzeroberfläche während der Operation reaktionsfähig,
und der Inhalt der TextBox kann ohne Einfrieren aktualisiert werden.
// MainWindow.xaml
<Window x:Class="AsyncExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Async / Await Beispiel" Height="250" Width="400">
<Grid Margin="20">
<StackPanel>
<TextBlock Text="Beispiel für asynchrone Operation" FontWeight="Bold" FontSize="16" Margin="0,0,0,10"/>
<TextBox x:Name="txtStatus" Height="30" Margin="0,0,0,10"
VerticalContentAlignment="Center" FontSize="14"/>
<Button x:Name="btnStart" Height="35" Content="Operation starten"
Click="btnStart_Click" FontSize="14"/>
</StackPanel>
</Grid>
</Window>
// MainWindow.xaml.cs
using System;
using System.Threading.Tasks;
using System.Windows;
namespace AsyncExample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
// async void ist hier zulässig, da es sich um einen Event-Handler handelt
txtStatus.Text = "Operation gestartet...";
btnStart.IsEnabled = false; // Schaltfläche deaktivieren
// Simulation einer lang andauernden Aufgabe (z. B. Datei-Download)
await Task.Delay(3000);
txtStatus.Text = "Operation abgeschlossen!";
btnStart.IsEnabled = true;
}
}
}
// Ablauf:
1. Der Benutzer klickt auf die Schaltfläche „Operation starten“.
2. Die TextBox wird mit „Operation gestartet...“ aktualisiert.
3. Es erfolgt eine 3-sekündige Verzögerung (UI bleibt reaktionsfähig).
4. Nach Abschluss wird die TextBox auf „Operation abgeschlossen!“ geändert.
Hinweis: Wenn dieser Code synchron (z. B. mit Thread.Sleep()) ausgeführt würde,
würde die Benutzeroberfläche einfrieren, und der Text „Operation gestartet...“
würde erst nach Abschluss der Aufgabe angezeigt.
await Task.Delay() beseitigt dieses Problem vollständig.
WPF-Beispiel: TextBox-Update aus einem Task (Verwendung von Dispatcher)
In diesem Beispiel wird eine lang andauernde Aufgabe in einem separaten Task ausgeführt.
Die Aktualisierung von txtStatus.Text erfolgt über den UI-Thread
mithilfe von Dispatcher.Invoke.
Dadurch werden Fehler beim Zugriff von mehreren Threads vermieden.
// MainWindow.xaml
<Window x:Class="AsyncExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Dispatcher Beispiel" Height="250" Width="400">
<Grid Margin="20">
<StackPanel>
<TextBlock Text="UI-Aktualisierung innerhalb eines Tasks" FontWeight="Bold" FontSize="16" Margin="0,0,0,10"/>
<TextBox x:Name="txtStatus" Height="30" Margin="0,0,0,10"
VerticalContentAlignment="Center" FontSize="14"/>
<Button x:Name="btnStart" Height="35" Content="Lange Operation starten"
Click="btnStart_Click" FontSize="14"/>
</StackPanel>
</Grid>
</Window>
// MainWindow.xaml.cs
using System;
using System.Threading.Tasks;
using System.Windows;
namespace AsyncExample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
btnStart.IsEnabled = false;
txtStatus.Text = "Prozess gestartet...";
// Lang andauernde Operation innerhalb eines Tasks ausführen
await Task.Run(() =>
{
for (int i = 1; i <= 5; i++)
{
// Arbeit simulieren
Task.Delay(1000).Wait();
// UI-Aktualisierung über Dispatcher
Dispatcher.Invoke(() =>
{
txtStatus.Text = $"Schritt {i} abgeschlossen...";
});
}
});
txtStatus.Text = "Alle Schritte abgeschlossen!";
btnStart.IsEnabled = true;
}
}
}
// Ablauf:
1. Auf die Schaltfläche „Lange Operation starten“ wird geklickt.
2. Der 5-stufige Prozess startet innerhalb von Task.Run.
3. Jede Sekunde wird die TextBox über Dispatcher.Invoke aktualisiert.
4. Die Benutzeroberfläche bleibt während jedes Schritts reaktionsfähig.
Hinweis:
Wenn Dispatcher.Invoke() durch direkten Zugriff auf txtStatus.Text = ... ersetzt würde,
würde ein Fehler wegen des Zugriffs von einem Nicht-UI-Thread auftreten.
Alternativ kann Dispatcher.BeginInvoke() verwendet werden,
wodurch der UI-Thread nicht blockiert wird.
TL;DR (Zusammenfassung)
asyncmacht eine Methode asynchron,awaitwartet auf den Abschluss der Aufgabe.TaskundTask<T>repräsentieren asynchrone Aufgaben und deren Ergebnisse.awaitblockiert die CPU nicht; die Ausführung wird fortgesetzt, sobald die Aufgabe abgeschlossen ist.- Mit
Task.WhenAll()können mehrere Aufgaben gleichzeitig ausgeführt werden. - Fehler werden mit
try-catchbehandelt;async voidsollte nur in Event-Handlern verwendet werden.
Ähnliche Artikel
Asynchrone Streams in C# (IAsyncEnumerable)
Lernen Sie asynchrone Streams in C# mit IAsyncEnumerable kennen, um Daten schrittweise effizient zu verarbeiten.
Prozess- und Threadverwaltung in C#
Lernen Sie Prozess- und Threadverwaltung in C#, um Ausführung, Ressourcen und Multithreading zu steuern.
Task Parallel Library (TPL) und Parallelprogrammierung in C#
Lernen Sie Task Parallel Library und Parallelprogrammierung in C# mit Task, Parallel und praktischen Beispielen.