1. Einführung
Fangen wir mit einer wichtigen Frage an: warum kann man sich nicht einfach auf asynchrone Methoden verlassen und hoffen, dass immer alles glatt läuft? Datei-Operationen werfen oft Exceptions — die Datei könnte gelöscht worden sein, der Speicher könnte knapp sein, es fehlen Berechtigungen oder die Datei ist gesperrt. Im synchronen Code würdet ihr Probleme im üblichen try-catch-Block abfangen. Im asynchronen Code ist die Philosophie die gleiche, aber es gibt Nuancen: ein Fehler kann nicht sofort beim Methodenaufruf auftreten, sondern später, wenn die Operation tatsächlich ausgeführt wird.
Zeitpunkt des Auftretens eines Fehlers
Im synchronen Code wird beim Lesen mit StreamReader.Read() die Exception direkt in der Aufrufzeile geworfen — gefangen im catch, und gut.
Im asynchronen Code (await stream.ReadAsync()) fällt der Fehler nicht beim Start der Operation, sondern genau beim await — also wenn die Task mit einem Fehler endet. Wenn man vergisst, await zu setzen, kann die Exception für eine Weile „unsichtbar“ bleiben.
2. Wie man Exceptions in asynchronen Methoden fängt
Schauen wir uns gleich ein typisches Muster an:
try
{
using FileStream fs = new FileStream("myfile.txt", FileMode.Open);
byte[] buffer = new byte[1024];
int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);
// Weitere Verarbeitung...
}
catch (IOException ex)
{
Console.WriteLine("Eingabe-/Ausgabe-Fehler: " + ex.Message);
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("Kein Zugriff auf die Datei: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Unbekannter Fehler: " + ex);
}
Ja, ganz einfach — wir verwenden das vertraute try-catch, aber in einer asynchronen Methode. Wichtig ist, dass die Methode selbst mit dem Schlüsselwort async markiert ist, sonst meckert der Compiler.
Wichtiger Hinweis: Wo das await setzen
Task<int> readTask = fs.ReadAsync(buffer, 0, buffer.Length);
// ... hier wurde versehentlich await oder Verarbeitung vergessen
In diesem Fall landet ein auftretender Fehler in der Task selbst (Task), und ihr erfahrt davon nicht, bis ihr versucht, das Ergebnis zu bekommen — z.B. via await readTask oder über die Eigenschaft Task.Exception. Wenn ihr das await komplett vergesst, kann die Task mit einem Fehler enden und niemand sagt euch Bescheid.
3. Warum asynchroner Code ohne Fehlerbehandlung tückisch ist
Szenario 1: „Fire and forget“ — Anfängerfalle
FileStream fs = new FileStream("file.txt", FileMode.Open);
byte[] buffer = new byte[8000];
fs.ReadAsync(buffer, 0, buffer.Length);
// Und das Programm macht normal weiter
Die Lese-Operation schwimmt sozusagen „parallel“ weiter, und wenn sie mit einem Fehler endet, fängt kein einziger catch sie. Die Exception versteckt sich innerhalb der Task. Dieses Pattern nennt man „fire and forget“ und in echten Anwendungen führt das leicht zu verlorenen kritischen Fehlern und Ressourcen-Leaks.
Szenario 2: Asynchrone Methoden ohne await
Task t = MyAsyncMethod();
// ... hier machen wir etwas und vergessen t
Fehler, die innerhalb von MyAsyncMethod passieren, werden nicht hochgemeldet, solange ihr nicht explizit auf die Task wartet (await t oder t.Wait()).
4. Wie man Fehler von asynchronen Tasks richtig fängt
Strategie 1: Benutzt immer await
try
{
await SomeFileOperationAsync();
}
catch (Exception ex)
{
Console.WriteLine("Etwas ist schiefgelaufen: " + ex.Message);
}
So wird die Exception genau an der Stelle geworfen, an der ihr wartet, und geht nicht verloren.
Strategie 2: Fehlerbehandlung mit .ContinueWith
Wenn ihr aus irgendeinem Grund kein await benutzt, könnt ihr einen Fehlerhandler über ContinueWith anhängen:
var task = fs.ReadAsync(buffer, 0, buffer.Length);
task.ContinueWith(t =>
{
if (t.Exception != null)
Console.WriteLine("Fehler beim asynchronen Lesen: " + t.Exception.InnerException);
}, TaskContinuationOptions.OnlyOnFaulted);
Ehrlich gesagt? In modernen C#-Anwendungen macht man das selten — async/await macht den Code einfacher und sauberer.
Mögliche Exceptions bei asynchroner Arbeit mit Dateien
- IOException — Plattenfehler, Datei nicht gefunden, zu langer Pfad, Gerät nicht verfügbar.
- UnauthorizedAccessException — fehlende Berechtigungen.
- ObjectDisposedException — Stream wurde geschlossen bevor die Operation fertig war.
- OperationCanceledException — Operation wurde über einen CancellationToken abgebrochen (CancellationToken).
5. Beispiel: Asynchrones Lesen mit Fehlerbehandlung
Fügen wir diese Logik in unsere Anwendung ein:
using System;
using System.IO;
using System.Threading.Tasks;
namespace FileAsyncDemo
{
class Program
{
static async Task Main()
{
string path = "bigfile.txt";
byte[] buffer = new byte[4096];
try
{
using FileStream fs = new FileStream(path, FileMode.Open);
int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);
Console.WriteLine($"Gelesen {bytesRead} Bytes aus {path}");
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Datei nicht gefunden: " + ex.Message);
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("Kein Zugriff auf die Datei: " + ex.Message);
}
catch (IOException ex)
{
Console.WriteLine("Lese-/Schreibfehler: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Anderer Fehler: " + ex.Message);
}
}
}
}
Wichtiger Hinweis
Wenn ihr in Main kein await verwendet, sondern nur Task result = SomeAsyncMethod(); — dann bleiben Fehler „stumm“ und zeigen sich erst später, wenn ihr das Ergebnis tatsächlich abruft.
6. Häufigste Fehler und Stolperfallen bei der Fehlerbehandlung
Man vergisst, das await auf eine asynchrone Methode zu setzen — Fehler tauchen nicht rechtzeitig auf, das Programm verhält sich unvorhersehbar.
Man wickelt asynchrone Aufrufe nicht in ein try-catch — die Anwendung stürzt beim ersten Fehler ab.
Man behandelt nur Exception und ignoriert spezifische Exceptions wie UnauthorizedAccessException oder OperationCanceledException — das führt zu schlechter Diagnose.
Man nutzt „fire and forget“-Tasks ohne explizites Logging der Fehler — Exceptions gehen in der Task verloren.
Man denkt, dass eine abgeschlossene Task automatisch Erfolg bedeutet. Man muss explizit auf das Ergebnis warten (await) und Exceptions behandeln.
GO TO FULL VERSION