JavaRush /Курси /C# SELF /Структура асинхронного методу (

Структура асинхронного методу ( async/ await)

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

1. Вступ

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

Асинхронний метод (async-метод) у .NET — це звичайний метод зі спеціальною «позначкою» у сигнатурі (async), яка дозволяє всередині використовувати оператор await для неблокуючого очікування інших асинхронних операцій (наприклад, роботи з мережею чи файлами) без блокування головного потоку. Саме ця «позначка» перетворює нудний синхронний магазин на суперсучасне обслуговування без черг.

Базова сигнатура асинхронного методу

Асинхронний метод обов’язково оголошується з модифікатором async у сигнатурі. Ось найпростіший приклад:

public async Task MyAsyncMethod()
{
    // Ваш асинхронний код тут
}

Типи, що повертаються, в асинхронних методах:

Тип, що повертається Опис Приклад
Task
Метод виконує роботу асинхронно, але не повертає результат.
public async Task SaveFileAsync()
Task<T>
Метод виконує роботу асинхронно й повертає результат типу T.
public async Task<int> GetSumAsync()
void
Використовується лише для подій. Не рекомендується в іншому коді!
public async void Button_Click(...)
ValueTask / ValueTask<T>
Для високопродуктивних сценаріїв, коли результат часто готовий синхронно.

Важливо: Не використовуйте async void ніде, окрім обробників подій! Інакше ризикуєте залишитися без коректної обробки помилок.

2. Простий приклад асинхронного методу

Повернімося до нашого тренувального застосунку (нехай це буде звичайна консольна програма) і додаймо асинхронний метод, який отримує дані з мережі. Зімітуємо затримку.

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine("Запуск асинхронного методу...");
        await MyFakeDownloadAsync();
        Console.WriteLine("Асинхронна операція завершена!");
    }

    static async Task MyFakeDownloadAsync()
    {
        Console.WriteLine("Починаємо завантаження (імітуємо затримку 2 с)...");
        await Task.Delay(2000); // Імітуємо довгу операцію
        Console.WriteLine("Завантаження завершене!");
    }
}

Коментарі до прикладу:

  • Ключове слово async зазначене у Main та MyFakeDownloadAsync.
  • Оператор await можна використовувати лише всередині методів, позначених як async.
  • Метод Task.Delay(2000) імітує довгу операцію (наприклад, мережевий запит).
  • Після появи оператора await потік не блокується — виконання Main «призупиняється» до завершення затримки.

3. Як працює асинхронний метод під капотом?

Коли компілятор бачить метод із ключовим словом async, він перетворює його на «машину станів», яка запам’ятовує місце зупинки, локальні змінні й продовжує виконання після завершення операції, на яку чекали через await.

Ось дуже спрощена схема:


+-----------------------------------------------------------+
|          Виклик асинхронного методу (наприклад, await X)  |
+-------------------------+---------------------------------+
                          |
                          v
            Операція не завершена? (не Task.Completed)
                |                  |
                | так              | ні
                v                  v
          Призупинити           Одразу повернути
          виконання,            результат
          запам'ятати           (або кинути помилку)
          контекст
                |
      Асинхронна операція завершується...
                |
                v
        Машина станів "пробуджує" метод,
        виконання продовжується з місця після await

Не обов’язково запам’ятовувати деталі реалізації: контекст (місце зупинки та локальні змінні) зберігається й автоматично відновлюється, коли операція завершиться.

Де можна (і не можна) використовувати ключові слова async і await

  • async ставиться перед оголошенням методу (або лямбди).
  • Усередині async-методу можна (і треба) використовувати await.
  • Якщо спробувати написати await у методі без async — буде помилка компіляції.
  • Якщо вказати async, але в методі немає жодного await, отримаєте попередження; метод виконається синхронно й поверне вже завершене завдання.

4. Варіанти значень, що повертаються, і їхні особливості

Результат Task

Якщо метод виконує асинхронні дії, але не повертає значення, використовуйте Task:

public async Task SaveToFileAsync(string path)
{
    await Task.Delay(1000);
    // Записуємо щось у файл
}

Результат Task<T>

Якщо потрібен результат, використовуйте Task<T>:

public async Task<int> CalculateAsync()
{
    await Task.Delay(500);
    return 42;
}

Виклик:

int result = await CalculateAsync();

Результат async void

Використовуйте лише для подій! Наприклад, обробник кліку кнопки:

private async void Button_Click(object sender, EventArgs e)
{
    await Task.Delay(1000);
    // Робимо щось іще
}

Чому не можна використовувати async void у звичайних методах? Тому що ви не дізнаєтеся, коли такий метод завершиться, і не зможете коректно спіймати винятки всередині нього.

Результати ValueTask і ValueTask<T>

З’явилися для високопродуктивних бібліотек, коли результат часто вже готовий синхронно — дозволяють уникнути зайвих виділень пам’яті для завдань. На початку кар’єри їх можна не використовувати: трапляються не так часто.

5. Приклад: асинхронний калькулятор із поверненням результату

Нехай програма рахує суму двох чисел «із затримкою», ніби це дуже складна операція:

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine("Введіть перше число:");
        int a = int.Parse(Console.ReadLine()!);

        Console.WriteLine("Введіть друге число:");
        int b = int.Parse(Console.ReadLine()!);

        Console.WriteLine("Виконуємо розрахунок...");
        int sum = await CalculateSumAsync(a, b);
        Console.WriteLine($"Сума: {sum}");
    }

    static async Task<int> CalculateSumAsync(int x, int y)
    {
        await Task.Delay(2000); // "Довга" операція
        return x + y;
    }
}

Що тут важливо:

  • CalculateSumAsync — асинхронний метод, повертає Task<int>, усередині є await.
  • Виклик у Main відбувається з використанням await (нагадування: починаючи з C# 7.1 метод Main може бути асинхронним).
  • Поки обчислюється сума, потік не блокується — можна показувати прогрес або виконувати іншу роботу.

6. Вкладені методи і вкладені await

Асинхронний метод може викликати інший асинхронний метод, а той — ще один. Увесь ланцюжок працює автоматично:

public async Task<int> StartWorkAsync()
{
    int data = await GetDataAsync();
    int result = await ProcessDataAsync(data);
    return result;
}

Усе просто: чекаємо завершення однієї операції, потім — іншої.

7. Пастки та часті помилки

1. Не забувайте async!
Якщо забути async, компілятор «образиться», побачивши всередині методу await, і не дасть зібрати код.

2. Не використовуйте async void у звичайних методах
Асинхронні void-методи (окрім обробників подій) — «чорні діри» для винятків. Помилки в них можуть «потонути», і ви про це навіть не дізнаєтеся.

3. Асинхронний метод БЕЗ await
Так можна, але безглуздо — метод виконається синхронно й поверне вже завершене завдання.

public async Task DoNothingAsync()
{
    // Немає жодного await!
}

4. Повернення Task із неасинхронного методу
Так роблять для уніфікації інтерфейсів: частина операцій справді асинхронні, частина — миттєві.

public Task<int> Foo()
{
    // return 42; // Так не можна!
    return Task.FromResult(42); // Можна, але жодної асинхронності тут немає.
}
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ