Async и Await в C#: как и когда использовать (пошаговый туториал)

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Async и Await в C#: спасаем приложение от «зависаний»

Вы когда-нибудь запускали программу на C#, нажимали кнопку, а интерфейс «замерзал» на несколько секунд? Или, может быть, ваш сервер переставал отвечать на новые запросы, пока обрабатывался один «тяжелый» запрос? Знакомо? Это классическая проблема синхронного кода, когда поток выполнения блокируется в ожидании завершения операции (чтения файла, запроса к базе данных, HTTP-вызова).

Решение этой проблемы — асинхронное программирование. И в C# главные инструменты для этого — ключевые слова async и await. Если вы когда-нибудь задавались вопросом: «Как и когда использовать async и await?» — вы попали по адресу. В этом туториале мы разберем всё от основ до лучших практик, чтобы вы писали быстрый, отзывчивый и масштабируемый код.

Этот материал основан на реальном популярном вопросе с Stack Overflow, собравшем тысячи просмотров. Мы адаптировали его для образовательного контента, добавив примеры кода и четкие объяснения.

Что такое async и await? Простыми словами

Представьте, что вы — шеф-повар (ваша программа). Вам нужно приготовить ужин. Одно из блюд — суп, который должен вариться 30 минут. В синхронном мире вы бы стояли у плиты и просто ждали, пока суп сварится, ничего не делая. Глупо, правда?

Асинхронность — это когда вы ставите суп вариться (async) и, не дожидаясь, начинаете резать салат, накрывать на стол и заниматься другими делами. Когда суп будет готов, вы возвращаетесь к нему (await). Вы не тратите время впустую — вы эффективны.

  • async — модификатор метода, который говорит компилятору: «Этот метод содержит асинхронные операции. Не блокируй поток, когда я встречу await».
  • await — оператор, который применяется к задаче (Task). Он говорит: «Приостанови выполнение этого метода, пока задача не завершится, но не блокируй поток. Как только задача завершится, продолжай выполнение с этого места».

Важно: await не блокирует поток. Он возвращает управление вызывающему коду, а поток продолжает выполнять другую работу.

Когда использовать async и await?

Это самый частый вопрос. Ответ прост: всегда, когда вы выполняете операцию, которая может занять много времени (I/O-bound операции), и вы не хотите блокировать поток.

Вот типичные сценарии:

  • Сетевые запросы: HTTP-вызовы (HttpClient), работа с WebSocket.
  • Работа с файлами: Чтение/запись больших файлов, работа с потоками.
  • Запросы к базам данных: Entity Framework, ADO.NET, Dapper.
  • Работа с веб-сервисами: WCF, REST API.
  • Любые операции ввода-вывода (I/O): Обращение к диску, сети, принтеру.

Когда НЕ нужно использовать async и await?

  • CPU-bound операции: Если ваша задача требует интенсивных вычислений (рендеринг 3D, обработка видео, сложные математические расчеты), async/await не поможет. Здесь нужно использовать Task.Run или Parallel для распараллеливания на несколько ядер процессора.
  • В конструкторах классов: Конструкторы не могут быть async. Используйте фабричные методы (public static async Task<MyClass> CreateAsync()).
  • В геттерах/сеттерах свойств: Они тоже не могут быть async. Используйте методы.

Как это работает? Разбор на примере кода

Давайте посмотрим на реальный код. Представьте, что мы загружаем данные с удаленного сервера.

Плохой пример (синхронный)

public string GetDataFromWeb()
{
    using (var client = new WebClient())
    {
        // Поток БЛОКИРУЕТСЯ на время загрузки
        string result = client.DownloadString("https://api.example.com/data");
        return result;
    }
}

Вызов этого метода в UI-приложении (WPF, WinForms) или в ASP.NET Core приведет к зависанию интерфейса или блокировке потока пула.

Хороший пример (асинхронный)

public async Task<string> GetDataFromWebAsync()
{
    using (var client = new HttpClient())
    {
        // Поток НЕ блокируется! Управление возвращается вызывающему коду.
        string result = await client.GetStringAsync("https://api.example.com/data");
        return result;
    }
}

Что здесь произошло?

  1. Метод помечен async.
  2. Он возвращает Task<string> — это «обещание» вернуть строку в будущем.
  3. Когда выполнение доходит до await client.GetStringAsync(...), метод приостанавливается и возвращает управление вызывающему коду.
  4. Поток, на котором выполнялся метод, освобождается и может обрабатывать другие запросы (например, другой HTTP-запрос к вашему серверу).
  5. Когда данные будут загружены, выполнение метода возобновится с того же места, и result будет присвоено значение.

Пошаговый туториал: от 0 до рабочего примера

Давайте напишем полноценное приложение для демонстрации. Создадим консольное приложение, которое загружает данные из трех разных источников.

Шаг 1: Создаем асинхронные методы

public class DataService
{
    private readonly HttpClient _httpClient = new HttpClient();

    public async Task<string> GetDataFromSource1Async()
    {
        Console.WriteLine("Начинаем загрузку из Source 1...");
        await Task.Delay(2000); // Симулируем задержку сети
        string data = await _httpClient.GetStringAsync("https://jsonplaceholder.typicode.com/posts/1");
        Console.WriteLine("Загрузка из Source 1 завершена.");
        return data;
    }

    public async Task<string> GetDataFromSource2Async()
    {
        Console.WriteLine("Начинаем загрузку из Source 2...");
        await Task.Delay(1500);
        string data = await _httpClient.GetStringAsync("https://jsonplaceholder.typicode.com/posts/2");
        Console.WriteLine("Загрузка из Source 2 завершена.");
        return data;
    }

    public async Task<string> GetDataFromSource3Async()
    {
        Console.WriteLine("Начинаем загрузку из Source 3...");
        await Task.Delay(1000);
        string data = await _httpClient.GetStringAsync("https://jsonplaceholder.typicode.com/posts                

Блоги

Рекомендаций по книгам