CodeGym /Kursy /C# SELF /Obsługa błędów w operacjach asynchronicznych (

Obsługa błędów w operacjach asynchronicznych ( async/ await)

C# SELF
Poziom 42, Lekcja 3
Dostępny

1. Wprowadzenie

Zacznijmy od ważnego pytania: dlaczego nie można po prostu zaufać metodom asynchronicznym i mieć nadzieję, że wszystko zawsze zadziała poprawnie? Operacje na plikach często rzucają wyjątki — plik mógł zostać usunięty, skończyła się pamięć, brakuje uprawnień albo plik jest zablokowany. W kodzie synchronicznym złapalibyście problemy w znanym bloku try-catch. W kodzie asynchronicznym filozofia jest taka sama, ale są niuanse: błąd może wystąpić nie od razu przy wywołaniu metody, lecz później, gdy operacja rzeczywiście się wykona.

Miejsce wystąpienia błędu

W kodzie synchronicznym przy czytaniu przez StreamReader.Read() wyjątek poleci bezpośrednio w linii wywołania — złapaliście w catch i OK.
W kodzie asynchronicznym (await stream.ReadAsync()) błąd spadnie nie w momencie startu operacji, lecz bezpośrednio przy await — kiedy task zakończy się z błędem. Jeśli zapomnicie postawić await, błąd może pozostać „niewidoczny” przez jakiś czas.

2. Jak łapać wyjątki w metodach asynchronicznych

Zaraz pokażemy typowy szablon:


try
{
    using FileStream fs = new FileStream("myfile.txt", FileMode.Open);
    byte[] buffer = new byte[1024];
    int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);
    // Dalsze przetwarzanie...
}
catch (IOException ex)
{
    Console.WriteLine("Błąd wejścia-wyjścia: " + ex.Message);
}
catch (UnauthorizedAccessException ex)
{
    Console.WriteLine("Brak dostępu do pliku: " + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("Nieznany błąd: " + ex);
}

Tak, tak, wszystko proste — używamy znanego try-catch, ale w metodzie asynchronicznej. Ważne, żeby sama metoda miała słowo kluczowe async, inaczej kompilator się zdenerwuje.

Ważny niuans: gdzie umieścić await


Task<int> readTask = fs.ReadAsync(buffer, 0, buffer.Length);
// ... tu przypadkowo zapomnieliśmy await albo obsługę

W takim przypadku błąd, jeśli się pojawi, trafi do samego zadania (Task), i nie dowiecie się o nim, dopóki nie spróbujecie dostać wyniku — np. przez await readTask lub przez właściwość Task.Exception. Jeśli w ogóle zapomnieliście o await — task może zakończyć się z błędem, a nikt wam o tym nie powie.

3. Dlaczego bez obsługi błędów kod asynchroniczny jest podstępny

Scenariusz 1: „Fire and forget” — pułapka dla początkujących


FileStream fs = new FileStream("file.txt", FileMode.Open);
byte[] buffer = new byte[8000];
fs.ReadAsync(buffer, 0, buffer.Length);
// A dalej program żyje swoim życiem

Operacja czytania wyrusza w „równoległy rejs”, i jeśli zakończy się błędem, żaden catch go nie złapie. Wyjątek schowa się wewnątrz taska. Ten wzorzec nazywa się „fire and forget” i w realnych aplikacjach grozi utratą krytycznych błędów i wyciekami zasobów.

Scenariusz 2: Metody asynchroniczne bez await


Task t = MyAsyncMethod();
// ... tu robimy coś, a potem zapomnieliśmy o t

Błędy, które wystąpiły wewnątrz MyAsyncMethod, nie wypłyną, dopóki wy wyraźnie nie poczekacie na zadanie (await t lub t.Wait()).

4. Jak poprawnie łapać błędy z zadań asynchronicznych

Strategia 1: Zawsze używaj await


try
{
    await SomeFileOperationAsync();
}
catch (Exception ex)
{
    Console.WriteLine("Coś poszło nie tak: " + ex.Message);
}

Dzięki temu wyjątek zostanie rzucony bezpośrednio w miejscu oczekiwania i nie zginie.

Strategia 2: Obsługa za pomocą .ContinueWith

Jeśli z jakiegoś powodu nie używacie await, można dodać handler błędów przez ContinueWith:


var task = fs.ReadAsync(buffer, 0, buffer.Length);
task.ContinueWith(t =>
{
    if (t.Exception != null)
        Console.WriteLine("Błąd przy asynchronicznym czytaniu: " + t.Exception.InnerException);
}, TaskContinuationOptions.OnlyOnFaulted);

Szczerze? W nowoczesnych aplikacjach C# rzadko się to robi — async/await robi kod prostszym i czytelniejszym.

Możliwe wyjątki przy pracy z plikami asynchronicznie

5. Przykład: Asynchroniczne czytanie z obsługą błędów

Dodajmy tę logikę do naszej aplikacji:


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($"Przeczytano {bytesRead} bajtów z {path}");
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine("Plik nie znaleziony: " + ex.Message);
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine("Brak dostępu do pliku: " + ex.Message);
            }
            catch (IOException ex)
            {
                Console.WriteLine("Błąd odczytu/zapisu: " + ex.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Inny błąd: " + ex.Message);
            }
        }
    }
}

Ważna uwaga

Jeśli nie używacie await w Main, a robicie tylko Task result = SomeAsyncMethod(); — błędy będą „milczeć” i pojawią się później, gdy jednak spróbujecie dostać wynik.

6. Najczęstsze błędy i „śliskie skórki” przy obsłudze błędów

Zapominanie o await na metodzie asynchronicznej — błędy nie wypływają na czas, program zachowuje się nieprzewidywalnie.

Nie owijać wywołań asynchronicznych w try-catch — aplikacja pada przy pierwszym błędzie.

Obsługiwać tylko Exception, ignorując specyficzne wyjątki typu UnauthorizedAccessException czy OperationCanceledException — w efekcie kiepska diagnostyka.

Używać „fire and forget” bez jawnego logowania błędów — wyjątki giną wewnątrz Task.

Sądzić, że jeśli task się zakończył — to wszystko OK. Trzeba wyraźnie czekać na wynik (await) i obsługiwać wyjątki.

2
Zadanie
C# SELF, poziom 42, lekcja 3
Niedostępne
Obsługa błędów podczas czytania pliku
Obsługa błędów podczas czytania pliku
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION