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, он превращает его в "машину состояния", которая запоминает место остановки, локальные переменные и продолжает выполнение после завершения awaited-операции.

Вот очень упрощенная схема:


+-----------------------------------------------------------+
|          Вызов асинхронного метода (например, 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); // Можно, но никакой асинхронности тут нет.
}
2
Задача
C# SELF, 59 уровень, 3 лекция
Недоступна
Асинхронная загрузка данных
Асинхронная загрузка данных
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ