JavaRush /Курси /C# SELF /Обробка помилок в асинхронних операціях (

Обробка помилок в асинхронних операціях ( async/ await)

C# SELF
Рівень 42 , Лекція 3
Відкрита

1. Вступ

Почнемо з важливого питання: чому не можна просто довіритися асинхронним методам і сподіватися, що все завжди працюватиме безвідмовно? Операції з файлами нерідко спричиняють винятки — файл могли видалити, закінчилася памʼять, бракує прав або файл заблокований. У синхронному коді ви б перехоплювали їх у звичному try-catch-блоці. В асинхронному коді принцип той самий, але є нюанси: виняток може виникнути не одразу під час виклику методу, а пізніше, коли операція фактично виконується.

Момент виникнення помилки

У синхронному коді під час читання через StreamReader.Read() виняток виникне прямо в рядку виклику — перехопили в catch — і гаразд.
В асинхронному коді (await stream.ReadAsync()) виняток зʼявиться не в момент старту операції, а безпосередньо під час await — коли задача завершиться з помилкою. Якщо забути додати await, помилка може залишатися «невидимою» певний час.

2. Як ловити винятки в асинхронних методах

Давайте одразу погляньмо на типовий шаблон:


try
{
    using FileStream fs = new FileStream("myfile.txt", FileMode.Open);
    byte[] buffer = new byte[1024];
    int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);
    // Подальша обробка...
}
catch (IOException ex)
{
    Console.WriteLine("Помилка введення-виведення: " + ex.Message);
}
catch (UnauthorizedAccessException ex)
{
    Console.WriteLine("Немає доступу до файлу: " + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("Невідома помилка: " + ex);
}

Так-так, усе просто — використовуємо знайомий try-catch, але в асинхронному методі. Важливо, щоб сам метод мав ключове слово async, інакше компілятор сваритиметься.

Важливий нюанс: де ставити await


Task<int> readTask = fs.ReadAsync(buffer, 0, buffer.Length);
// ... тут випадково забули await або обробку

У такому разі помилка, якщо вона станеться, потрапить у саму задачу (Task), і ви про неї не дізнаєтеся, доки не спробуєте отримати результат — наприклад, через await readTask або через властивість Task.Exception. Якщо взагалі забути про await, задача може завершитися з помилкою, а вам про це ніхто не повідомить.

3. Чому без обробки помилок асинхронний код підступний

Сценарій 1: «Fire and forget» — пастка новачка


FileStream fs = new FileStream("file.txt", FileMode.Open);
byte[] buffer = new byte[8000];
fs.ReadAsync(buffer, 0, buffer.Length);
// А далі програма живе собі своїм життям

Операція читання йде у «паралельне плавання», і якщо вона завершиться з помилкою, жоден catch її не перехопить. Виняток сховається всередині задачі. Цей патерн називають «fire and forget», і в реальних застосунках він загрожує пропуском критичних помилок і витоками ресурсів.

Сценарій 2: Асинхронні методи без await


Task t = MyAsyncMethod();
// ... тут щось робимо, а потім забули про t

Помилки, що сталися всередині MyAsyncMethod, не «піднімуться», доки ви явно не дочекаєтеся задачі (await t або t.Wait()).

4. Як правильно ловити помилки від асинхронних задач

Стратегія 1: Завжди використовуйте await


try
{
    await SomeFileOperationAsync();
}
catch (Exception ex)
{
    Console.WriteLine("Щось пішло не так: " + ex.Message);
}

Так виняток буде піднято саме в місці очікування і не загубиться.

Стратегія 2: Обробка за допомогою .ContinueWith

Якщо з якоїсь причини ви не використовуєте await, можна додати обробник помилок через ContinueWith:


var task = fs.ReadAsync(buffer, 0, buffer.Length);
task.ContinueWith(t =>
{
    if (t.Exception != null)
        Console.WriteLine("Помилка під час асинхронного читання: " + t.Exception.InnerException);
}, TaskContinuationOptions.OnlyOnFaulted);

Відверто кажучи, у сучасних застосунках C# так роблять рідко — async/await робить код простішим і чистішим.

Можливі винятки під час роботи з файлами асинхронно

  • IOException — збій диска, файл не знайдено, надто довгий шлях, пристрій недоступний.
  • UnauthorizedAccessException — недостатньо прав доступу.
  • ObjectDisposedException — потік закрито до завершення операції.
  • OperationCanceledException — операцію скасовано через токен скасування (CancellationToken).

5. Приклад: Асинхронне читання з обробкою помилок

Додаймо цю логіку в наш застосунок:


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($"Прочитано {bytesRead} байт із {path}");
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine("Файл не знайдено: " + ex.Message);
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine("Немає доступу до файлу: " + ex.Message);
            }
            catch (IOException ex)
            {
                Console.WriteLine("Помилка читання/запису: " + ex.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Інша помилка: " + ex.Message);
            }
        }
    }
}

Важливе зауваження

Якщо ви не використовуєте await у Main, а робите лише Task result = SomeAsyncMethod(); — помилки «мовчатимуть» і виявляться пізніше, коли ви таки спробуєте отримати результат.

6. Найчастіші помилки й «граблі» під час обробки помилок

Забувають ставити await на асинхронний метод — помилки не спливають вчасно, програма поводиться непередбачувано.

Не обгортають асинхронні виклики в try-catch — застосунок падає при першому ж збої.

Обробляють лише Exception, ігноруючи специфічні винятки на кшталт UnauthorizedAccessException або OperationCanceledException — у підсумку це ускладнює діагностику.

Використовують «fire and forget» задачі без явного логування помилок — винятки губляться всередині Task.

Вважають, що якщо задача завершилася — отже, усе успішно. Потрібно явно очікувати результат (await) й обробляти винятки.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ