← Назад к вопросам

В чём разница между асинхронным вызовом и асинхронным вызовом с использованием await?

2.0 Middle🔥 281 комментариев
#Асинхронность и многопоточность

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разница между асинхронным вызовом и использованием await

Ключевое различие между обычным асинхронным вызовом (например, через Task.ContinueWith или callback-и) и асинхронным вызовом с await заключается в парадигме программирования: первое следует асинхронной модели на основе колбэков, второе — асинхронной модели на основе задач (Task-based Asynchronous Pattern, TAP) с синхронным стилем кода.

1. Обычный асинхронный вызов (без await)

Это подход, где вы запускаете операцию и подписываетесь на её завершение через callback, продолжение (ContinueWith) или ручное ожидание. Код становится "разорванным" и сложным для чтения.

public void GetDataOldSchool()
{
    var httpClient = new HttpClient();
    httpClient.GetStringAsync("https://api.example.com/data")
        .ContinueWith(task =>
        {
            if (task.IsCompletedSuccessfully)
            {
                string result = task.Result;
                ProcessData(result);
            }
            else
            {
                // Обработка ошибок
            }
        }, TaskScheduler.FromCurrentSynchronizationContext());
}

Недостатки:

  • Сложность вложенности (callback hell).
  • Ручное управление контекстом синхронизации (например, для UI).
  • Трудоёмкая обработка исключений (нужно проверять IsFaulted, Exception).
  • Отсутствие естественного потока выполнения — код "прыгает" между обработчиками.

2. Асинхронный вызов с await

Ключевое слово await, введённое в C# 5.0, позволяет писать асинхронный код, который выглядит как синхронный, но не блокирует поток.

public async Task GetDataModernAsync()
{
    try
    {
        var httpClient = new HttpClient();
        string result = await httpClient.GetStringAsync("https://api.example.com/data");
        ProcessData(result);
    }
    catch (HttpRequestException ex)
    {
        // Естественная обработка исключений
        LogError(ex);
    }
}

Преимущества подхода с await:

  • Читаемость и линейность кода — логика выполнения соответствует порядку операций.
  • Автоматическое сохранение контекста синхронизации — после await код продолжает выполняться в том же контексте (например, в UI-потоке), если не указано иное через ConfigureAwait(false).
  • Естественная обработка исключений через try/catch.
  • Эффективное использование потоков — во время ожидания IO-операции поток освобождается.
  • Композиция асинхронных операций — легко комбинировать несколько await-вызовов.

3. Ключевые технические отличия

АспектБез await (колбэки)С await
Поток выполненияРазрывной, через обработчикиЛинейный, как в синхронном коде
Обработка ошибокРучная проверка статуса TaskСтандартные try/catch блоки
Контекст синхронизацииРучное управлениеАвтоматическое сохранение (по умолчанию)
Состояние методаНе сохраняетсяКомпилятор генерирует машину состояний
ЧитаемостьНизкая, особенно при цепочках вызововВысокая, логика последовательна

4. Что происходит "под капотом" с await

Компилятор C# преобразует async/await в машину состояний (state machine):

// Упрощённое представление того, что генерирует компилятор
[AsyncStateMachine(typeof(GetDataModernAsyncStateMachine))]
public Task GetDataModernAsync()
{
    var stateMachine = new GetDataModernAsyncStateMachine();
    stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
    stateMachine.<>1__state = -1;
    stateMachine.<>t__builder.Start(ref stateMachine);
    return stateMachine.<>t__builder.Task;
}

Ключевые этапы:

  1. При встрече await метод возвращает управление вызывающему коду.
  2. Выполняется асинхронная операция без блокировки текущего потока.
  3. После завершения операции выполнение возобновляется с точки останова.
  4. Стек вызовов сохраняется, что невозможно при использовании колбэков.

5. Когда что использовать

Используйте await всегда, когда возможно, так как это:

  • Современный стандарт для асинхронного программирования в .NET
  • Упрощает поддержку и отладку кода
  • Предотвращает блокировку потоков (особенно важное для UI и веб-серверов)

Более низкоуровневый подход (без await) может потребоваться для:

  • Оптимизации в высоконагруженных сценариях (микрооптимизации)
  • Работы с legacy-кодом или API, не поддерживающими TAP
  • Специфических сценариев, где нужно контролировать каждый аспект выполнения

6. Важное предостережение

await не делает операцию асинхронной — он лишь предоставляет удобный способ ожидания уже асинхронной операции. Сама асинхронность обеспечивается методом, который возвращает Task или Task<T>:

// Правильно: метод API является асинхронным
await httpClient.GetStringAsync(...);

// Неправильно: await не сделает синхронный метод асинхронным
await Task.Run(() => SomeSynchronousMethod()); // Это всего лишь обёртка

В современном C# разработке async/await стал де-факто стандартом для асинхронного программирования, заменив старые паттерны на основе колбэков и Begin/End методов благодаря сочетанию производительности, читаемости и безопасности.

В чём разница между асинхронным вызовом и асинхронным вызовом с использованием await? | PrepBro