C# Async / Await

6 Kernkonzepte
Die wichtigsten Konzepte für asynchrone Programmierung in C#: async · await · Task · CancellationToken · WhenAll / WhenAny · ConfigureAwait Async/Await ist das Herzstück der asynchronen Programmierung in C#. Es ermöglicht die Entwicklung reaktionsfähiger Anwendungen ohne Blockierung des Hauptthreads.

async / await – Asynchrone Methoden

async Task · await · return
// Asynchrone Methode public async Task<string> FetchDataAsync() { await Task.Delay(1000); // Simuliert asynchrone Arbeit return "Daten geladen"; }

async kennzeichnet eine Methode als asynchron. await wartet auf den Abschluss einer asynchronen Operation, ohne den Thread zu blockieren. Der Rückgabetyp ist Task (für void) oder Task<T> (für Werte).

Wichtige Regeln

Regel Beschreibung
async Task Für Methoden ohne Rückgabewert (void)
async Task<T> Für Methoden mit Rückgabewert vom Typ T
await Nur in async-Methoden verwendbar
Naming Konvention: Methodenname endet mit "Async"
return Gibt den Wert zurück (bei Task<T>)
Beispiele
// Asynchrone Methode mit Task<string>
public async Task<string> DownloadStringAsync(string url) {
using var client = new HttpClient();
var response = await client.GetStringAsync(url);
return response;
}
// Asynchrone Methode ohne Rückgabewert
public async Task SaveDataAsync(string data) {
await Task.Run(() => {
// Daten speichern (CPU-gebunden)
Thread.Sleep(500);
});
}
// Aufruf
string result = await DownloadStringAsync("https://example.com");
Tipp: Async-Methoden sollten "Async" als Namens-Suffix haben (Konvention). Vermeiden Sie async void – verwenden Sie async Task für bessere Fehlerbehandlung.

Task & Task<T> – Repräsentation asynchroner Arbeit

Task · Task.Run · Task.FromResult
// Task ohne Rückgabewert Task task = Task.Run(() => { /* Arbeit */ }); // Task mit Rückgabewert (Task<T>) Task<int> task = Task.Run(() => { return 42; }); // Bereits abgeschlossener Task Task<string> task = Task.FromResult("Fertig");

Task repräsentiert eine asynchrone Operation. Task<T> repräsentiert eine asynchrone Operation, die einen Wert vom Typ T zurückgibt. Tasks können mit Task.Run() gestartet oder über Task.FromResult() bereits abgeschlossen sein.

Beispiele
// Task erstellen und starten
Task task = Task.Run(() => {
Console.WriteLine("Arbeite im Hintergrund...");
Thread.Sleep(1000);
});
await task; // Warten bis Task abgeschlossen
// Task<T> mit Rückgabewert
Task<int> calcTask = Task.Run(() => {
return 42 + 13;
});
int result = await calcTask; // 55
// Task.FromResult (synchroner Wert)
public Task<string> GetDataAsync() {
return Task.FromResult("Daten");
}
// Status prüfen
if (task.IsCompleted) {
Console.WriteLine("Task abgeschlossen");
}
Tipp: Task.Run() startet eine Operation auf dem ThreadPool. Für I/O-gebundene Operationen verwenden Sie besser die nativen Async-Methoden (z.B. HttpClient.GetStringAsync).

CancellationToken – Asynchrone Operationen abbrechen

CancellationToken · ThrowIfCancellationRequested
// Token erstellen using var cts = new CancellationTokenSource(); var token = cts.Token; // Abbruch anfordern cts.Cancel(); // In asynchroner Methode public async Task LongRunningAsync(CancellationToken token) { token.ThrowIfCancellationRequested(); }

CancellationToken ermöglicht das kooperative Abbrechen asynchroner Operationen. Die Operation prüft regelmäßig, ob ein Abbruch angefordert wurde, und beendet sich selbst.

Beispiele
// Lang laufende Operation mit Abbruch
public async Task ProcessDataAsync(CancellationToken token) {
for (int i = 0; i < 100; i++) {
token.ThrowIfCancellationRequested(); // Prüft Abbruch
await Task.Delay(100, token); // Auch Delay kann abbrechen
Console.WriteLine(i);
}
}
// Verwendung mit Timeout
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try {
await ProcessDataAsync(cts.Token);
} catch (OperationCanceledException) {
Console.WriteLine("Abbruch erfolgt");
}
Tipp: Verwenden Sie ThrowIfCancellationRequested() für eine saubere Abbruchbehandlung. Für Timeouts nutzen Sie CancellationTokenSource(TimeSpan).

WhenAll / WhenAny – Mehrere Tasks kombinieren

Task.WhenAll · Task.WhenAny
// Alle Tasks abschließen await Task.WhenAll(task1, task2, task3); // Einen Task abschließen Task first = await Task.WhenAny(task1, task2);

Task.WhenAll wartet auf die Fertigstellung aller angegebenen Tasks. Task.WhenAny wartet auf die Fertigstellung des ersten Tasks (wird in Rennen verwendet).

Beispiele
// WhenAll: Alle parallel laden
var tasks = new List<Task<string>>();
tasks.Add(FetchDataAsync("url1"));
tasks.Add(FetchDataAsync("url2"));
tasks.Add(FetchDataAsync("url3"));
string[] results = await Task.WhenAll(tasks);
// WhenAny: Erste Antwort verwenden
Task<string> fastest = await Task.WhenAny(tasks);
string result = await fastest;
// WhenAll mit Timeout
var timeout = Task.Delay(TimeSpan.FromSeconds(10));
var completed = await Task.WhenAny(Task.WhenAll(tasks), timeout);
if (completed == timeout) {
Console.WriteLine("Zeitüberschreitung");
}
Tipp: Mit Task.WhenAny können Sie Timeouts implementieren oder die schnellste Antwort aus mehreren Quellen verwenden.

Fehlerbehandlung – try-catch in async

try · catch · finally · AggregateException
// Fehlerbehandlung in async try { string result = await FetchDataAsync(); } catch (HttpRequestException ex) { Console.WriteLine($"Netzwerkfehler: {ex.Message}"); }

Fehlerbehandlung in asynchronen Methoden funktioniert ähnlich wie bei synchronen Methoden – mit try/catch. Bei Task.WhenAll können mehrere Fehler in einer AggregateException auftreten.

Beispiele
// Einfache Fehlerbehandlung
try {
var data = await DownloadStringAsync("https://invalid.url");
} catch (HttpRequestException ex) {
Console.WriteLine($"HTTP-Fehler: {ex.Message}");
} catch (Exception ex) {
Console.WriteLine($"Allgemeiner Fehler: {ex.Message}");
}
// WhenAll Fehler (AggregateException)
var tasks = new List<Task> { FaultyTask1(), FaultyTask2() };
try {
await Task.WhenAll(tasks);
} catch (AggregateException ae) {
foreach (var ex in ae.InnerExceptions) {
Console.WriteLine(ex.Message);
}
}
// Finally (immer ausgeführt)
try {
await DoWorkAsync();
} finally {
Console.WriteLine("Aufräumarbeiten");
}
Tipp: Bei Task.WhenAll werden alle Fehler in einer AggregateException gesammelt. Verwenden Sie await für jeden Task einzeln, um sofort auf den ersten Fehler zu reagieren.

ConfigureAwait – Synchronisationskontext steuern

ConfigureAwait(false) · SynchronizationContext
// Ohne Synchronisationskontext fortsetzen await SomeAsyncMethod().ConfigureAwait(false); // In Bibliotheken empfohlen public async Task<string> GetDataAsync() { var result = await httpClient.GetStringAsync(url).ConfigureAwait(false); return result; }

ConfigureAwait(false) verhindert, dass die Fortsetzung nach dem await zum ursprünglichen Synchronisationskontext zurückkehrt. Dies verbessert die Leistung und verhindert Deadlocks in Bibliotheken.

Beispiele
// In Bibliotheken (verhindert Deadlocks)
public async Task<string> FetchFromApiAsync() {
using var client = new HttpClient();
var response = await client.GetStringAsync("https://api.example.com")
.ConfigureAwait(false);
return response;
}
// In UI-Anwendungen (ohne ConfigureAwait)
public async void LoadButton_Click(object sender, EventArgs e) {
// Ohne ConfigureAwait(false) bleibt UI-Thread
var data = await FetchFromApiAsync();
textBox.Text = data; // Zugriff auf UI-Elemente sicher
}
Tipp: In Bibliotheken und Hintergrunddiensten immer ConfigureAwait(false) verwenden. In UI-Anwendungen (WPF, WinForms) kann darauf verzichtet werden, um den UI-Thread für UI-Updates zu behalten.

C# Async/Await im Überblick

async Methode ist asynchron
Rückgabetyp: Task
await Auf Task warten (nicht blockierend)
Nur in async-Methoden
Task Asynchrone Operation
Task<T> für Werte
Cancel Abbruchtoken
CancellationTokenSource
WhenAll Alle Tasks abschließen
Parallelität
ConfigureAwait Kontext steuern
false für Libraries

Quick Summary

async
Asynchrone Methode
Task
Asynchrone Operation
Cancel
Abbruch
WhenAll
Tasks kombinieren
try-catch
Fehlerbehandlung
ConfigureAwait
Kontext steuern
public async Task<string> FetchAsync() { await ... } · await Task.WhenAll(tasks)