Programme warten oft: auf Netzwerk, Datenbank, Datei. Mit async/await warten sie, ohne einen Thread zu blockieren: Währenddessen kann das Programm andere Arbeit erledigen (Oberflächen bleiben bedienbar, Server bedienen mehr Anfragen).
Console.WriteLine("start");
string ergebnis = await LadeAsync("Daten");
Console.WriteLine(ergebnis);
async Task<string> LadeAsync(string name)
{
await Task.Delay(100); // simuliert Wartezeit, blockiert keinen Thread
return $"{name} geladen";
}Ausgabe
start Daten geladen
asyncmarkiert eine Methode, in derawaiterlaubt istTask(ohne Ergebnis) undTask<T>(mit Ergebnis) beschreiben eine laufende Aufgabeawaitwartet auf das Ergebnis und führt danach den Rest der Methode weiter aus- In Konsolen-Programmen mit Top-Level-Anweisungen darfst du direkt
awaitbenutzen
Mehrere Aufgaben parallel
var uhr = System.Diagnostics.Stopwatch.StartNew();
// nacheinander
string a = await Aufgabe("A", 100);
string b = await Aufgabe("B", 100);
Console.WriteLine($"nacheinander: {a} {b} (>= 200 ms: {uhr.ElapsedMilliseconds >= 190})");
// parallel
uhr.Restart();
var t1 = Aufgabe("A", 100);
var t2 = Aufgabe("B", 100);
string[] alle = await Task.WhenAll(t1, t2);
Console.WriteLine($"parallel: {string.Join(" ", alle)} (< 190 ms: {uhr.ElapsedMilliseconds < 190})");
string erste = await await Task.WhenAny(Aufgabe("langsam", 200), Aufgabe("schnell", 20));
Console.WriteLine(erste);
async Task<string> Aufgabe(string name, int ms) { await Task.Delay(ms); return name; }Ausgabe
nacheinander: A B (>= 200 ms: True) parallel: A B (< 190 ms: True) schnell
Starte unabhängige Aufgaben zuerst und warte erst dann (Task.WhenAll), statt jede einzeln hintereinander zu erwarten.
Fehler in async-Methoden
try
{
await Kaputt();
}
catch (InvalidOperationException e)
{
Console.WriteLine("gefangen: " + e.Message);
}
try { await Task.WhenAll(Kaputt(), Task.Delay(10)); }
catch (Exception e) { Console.WriteLine(e.GetType().Name); }
async Task Kaputt()
{
await Task.Delay(10);
throw new InvalidOperationException("etwas ging schief");
}Ausgabe
gefangen: etwas ging schief InvalidOperationException
Abbruch und Zeitlimit
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50));
try
{
await LangeAufgabe(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("abgebrochen");
}
async Task LangeAufgabe(CancellationToken token)
{
for (int i = 0; i < 100; i++)
{
token.ThrowIfCancellationRequested();
await Task.Delay(20, token);
}
}Ausgabe
abgebrochen
Gib langen Aufgaben einen CancellationToken mit, damit sie sich abbrechen lassen.
Datenströme: IAsyncEnumerable
await foreach (var zahl in Zahlen())
Console.Write(zahl + " ");
Console.WriteLine();
async IAsyncEnumerable<int> Zahlen()
{
for (int i = 1; i <= 5; i++)
{
await Task.Delay(10);
yield return i * i;
}
}Ausgabe
1 4 9 16 25
HTTP-Anfragen
Netzwerkzugriffe sind der Hauptanwendungsfall von async:
using var client = new HttpClient();
string json = await client.GetStringAsync("https://example.org/api/daten");
var antwort = await client.GetAsync("https://example.org/api/daten");
if (!antwort.IsSuccessStatusCode) throw new Exception($"HTTP {(int) antwort.StatusCode}");
var nutzer = await antwort.Content.ReadFromJsonAsync<Nutzer>();Erzeuge einen HttpClient einmal und verwende ihn wieder (oder IHttpClientFactory in ASP.NET Core).
Regeln
- Async bis ganz oben:
asynczieht sich durch die Aufrufkette. Vermeide.Resultund.Wait()(Deadlock-Gefahr). - Methodennamen enden auf
Async(LadeDatenAsync). async voidnur für Event-Handler, sonstasync Task.Task.Run(...)verlagert rechenintensive Arbeit auf einen Pool-Thread. Für Warten (I/O) braucht man das nicht.- In Bibliotheken
ConfigureAwait(false)verwenden (nicht in ASP.NET Core Anwendungen und UI-Code nötig). - Mit Locks (
lock) darf man nichtawaiten: nimmSemaphoreSlim.
Threads und Parallelität
int summe = 0;
Parallel.For(1, 1001, i => Interlocked.Add(ref summe, i)); // CPU-Arbeit parallel
Console.WriteLine(summe);
var ergebnis = Enumerable.Range(1, 10).AsParallel().Select(x => x * x).OrderBy(x => x).ToArray();
Console.WriteLine(string.Join(",", ergebnis));
object sperre = new();
int zaehler = 0;
await Task.WhenAll(Enumerable.Range(0, 4).Select(_ => Task.Run(() =>
{
for (int i = 0; i < 10000; i++) lock (sperre) zaehler++;
})));
Console.WriteLine(zaehler);Ausgabe
500500 1,4,9,16,25,36,49,64,81,100 40000
Merke
async/awaitwartet, ohne Threads zu blockieren;Task<T>ist das Versprechen eines Ergebnisses- Unabhängiges zuerst starten, dann
Task.WhenAll - Fehler fängst du mit
try/catchrund umawait CancellationTokenfür Abbruch und Zeitlimits- Kein
.Result/.Wait();async Taskstattasync void Parallel.For,Interlocked,lockfür CPU-Arbeit und gemeinsame Daten
Aufgabe
Lade (simuliert mit Task.Delay) drei "Webseiten" parallel und gib die Gesamtdauer aus.