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

Async/Await: Предсказать вывод программы

1.8 Middle🔥 121 комментариев
#Основы C# и .NET

Условие

Дан следующий код:

class Program { private static string result; static void Main() { SaySomething(); Console.WriteLine(result); } static async Task<string> SaySomething() { await Task.Delay(5); result = "Hello world!"; return "Something"; } }

Задание:

  1. Что выведет программа и почему?
  2. Что изменится, если заменить await Task.Delay(5) на Thread.Sleep(5)?
  3. Как исправить код, чтобы вывелось Hello world?

Требования к ответу:

  • Объяснить механизм работы async/await
  • Показать понимание разницы между асинхронным и синхронным ожиданием

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

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

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

Решение

Что выведет программа?

Вывод: Ничего (пустая строка) или (nothing). Переменная result остаётся null.

Почему это происходит:

Это классическая ошибка при работе с async/await. Проблема в том, что:

  1. Метод SaySomething() имеет сигнатуру async Task<string> (возвращает Task)
  2. Вызов SaySomething() в методе Main() не ждёт завершения асинхронной операции
  3. Программа продолжает выполнение сразу же, не дожидаясь await Task.Delay(5)
  4. Main() синхронный метод, он не может использовать await
  5. Поток выполнения программы завершается, так как Main() закончился, до того, как задача из SaySomething() выполнится
Мегапоток выполнения:
1. Вызов SaySomething() — запускается асинхронная задача, но НЕ ЖДЁМ
2. Console.WriteLine(result) — result всё ещё null, выводим ничего
3. Main завершается → программа завершается
4. (в фоне) Задача из SaySomething() пытается выполниться, но их уже нет

Механизм async/await

Важно понимать, как работает async/await:

// ЭТО:
async Task<string> SaySomething()
{
    await Task.Delay(5);
    result = "Hello world!";
    return "Something";
}

// КОМПИЛИРУЕТСЯ ПРИМЕРНО В ЭТО (state machine):
Task<string> SaySomething()
{
    var stateMachine = new SaySomethingStateMachine();
    stateMachine.builder = AsyncTaskMethodBuilder<string>.Create();
    stateMachine.state = 0;
    stateMachine.builder.Start(ref stateMachine);
    return stateMachine.builder.Task;
}

Когда мы используем await, компилятор создаёт state machine, который:

  • Приостанавливает выполнение метода в точке await
  • Возвращает Task вызывающему коду
  • Продолжает выполнение, когда Task завершится

Что изменится, если заменить await Task.Delay(5) на Thread.Sleep(5)?

Вывод: Hello world!

Почему:

static async Task<string> SaySomething()
{
    Thread.Sleep(5);  // Синхронное ожидание
    result = "Hello world!";
    return "Something";
}

Chaincode:

  1. SaySomething() вызывается
  2. Thread.Sleep(5) блокирует текущий поток НА 5 МИЛЛИСЕКУНД (но это основной поток)
  3. result = "Hello world!" выполняется СРАЗУ ЖЕ (в том же потоке)
  4. Main() продолжает выполнение и видит уже установленное значение result
  5. Вывод: Hello world!

Важное замечание: Хотя метод помечен как async, использование Thread.Sleep() полностью нивелирует преимущества асинхронности. Это блокирует поток, и это плохая практика. Но в этом конкретном случае программа будет работать, потому что основной поток достаточно долго ждёт.

Как исправить код?

Вариант 1: Сделать Main асинхронным (рекомендуется для современного C#)

class Program
{
    private static string result;
    
    static async Task Main()  // async Task Main доступен с C# 7.1+
    {
        await SaySomething();
        Console.WriteLine(result);
    }
    
    static async Task<string> SaySomething()
    {
        await Task.Delay(5);
        result = "Hello world!";
        return "Something";
    }
}

Вывод: Hello world!

Это лучший подход, так как:

  • Поддерживает асинхронность на всех уровнях
  • Используется современный синтаксис
  • Нет блокировок потока

Вариант 2: Ждать завершения Task в синхронном Main

static void Main()
{
    SaySomething().Wait();  // Синхронное ожидание Task
    Console.WriteLine(result);
}

Вывод: Hello world!

Проблемы:

  • .Wait() блокирует поток (может вызвать deadlock в UI приложениях)
  • Может скрывать исключения
  • Не асинхронно

Вариант 3: Использовать Result

static void Main()
{
    var task = SaySomething();
    task.Result;  // Блокирующее получение результата
    Console.WriteLine(result);
}

Вывод: Hello world!

Проблемы: Те же, что и у .Wait(), плюс может переупаковать исключение в AggregateException.

Ключевые различия между вариантами

ПодходБлокирует поток?Async?Рекомендуется?
await в async MainНетДа✅ Всегда
.Wait()ДаНет❌ Избегать
.ResultДаНет❌ Избегать
Thread.Sleep()ДаНет❌ Никогда

Распространённые ошибки

// ❌ Ошибка 1: Забыли await
static async Task Main()
{
    SaySomething();  // Создали Task, но не ждём
    Console.WriteLine(result);  // result ещё null
}

// ❌ Ошибка 2: Sync over async — anti-pattern
static void Main()
{
    SaySomething().Result;  // Блокировка, может deadlock
}

// ✅ Правильно
static async Task Main()
{
    await SaySomething();
    Console.WriteLine(result);
}

Почему это важно для backend-разработчика?

  1. Масштабируемость: Асинхронный код позволяет обслуживать больше одновременных запросов с одинаковым количеством потоков
  2. Производительность: Не блокируем потоки при ожидании I/O операций
  3. Отзывчивость: UI остаётся отзывчивым при асинхронных операциях
  4. Best practices: Современный .NET код всегда асинхронный

Итог

  • Программа выведет пустую строку (result = null)
  • С Thread.Sleep() выведет Hello world!
  • Исправить нужно, сделав Main асинхронным: static async Task Main()
  • Правило: если вызываете асинхронный метод, ВСЕГДА используйте await