Async/Await — не про многопоточность

Думаю, не ошибусь, если скажу, что специалисты разного возраста говорят о разном, когда упоминают многопоточность в .NET. Те, кто начинал работу с .NET давно, мыслили в терминах «потоков». Те, кто уже начали с async/await, часто понимают под этим просто управление ожиданием, не связанное с управлением потоками…

Давайте вспомним разные эпизоды истории синхронизации.

Как мы ждали раньше: блокировка как норма

В раннем .NET (* и не только в нём, я помню очень большой проект на C++ с той же парадигмой многопоточности) ожидание выглядело просто:

  • поток зашёл в метод
  • метод делает работу
  • поток ждёт, пока работа завершится

Если операция была долгой (I/O, сеть, диск):

  • поток блокировался
  • ресурсы простаивали
  • UI зависал
  • сервер «съедал» потоки

Типичные инструменты того времени:

  • lock
  • Monitor
  • WaitHandle
  • Thread.Sleep
  • ручное управление потоками

Ожидание = занятый поток.

Асинхронность до async/await: боль и колбэки

Когда стало понятно, что блокировать потоки дорого, появились асинхронные API:

  • конструкция с вызовами begin и end (Begin/End pattern),
  • callbacks,
  • события,
  • IAsyncResult.

Примерно так:

  • операция стартует,
  • управление возвращается сразу,
  • при завершении вызывается callback.

Технически — эффективно.
Практически — нечитаемо и хрупко.

Код:

  • терял линейность,
  • размазывался по методам,
  • усложнял обработку ошибок.

Что изменил async/await

async/await не добавил новых возможностей, он:

  • переписал асинхронность в нормальный синтаксис
  • сохранил линейное мышление
  • убрал ручное управление продолжениями

Ключевая идея:

Поток не ждёт. Ждёт состояние операции.

Когда выполнение доходит до await:

  1. если результат готов — код идёт дальше
  2. если нет — метод приостанавливается
  3. поток освобождается
  4. продолжение выполняется позже

Где здесь потоки — а где нет

Это самый важный момент.

  • await не создаёт поток
  • await не гарантирует другой поток
  • await не означает параллельность

Он означает только одно:

«Продолжи выполнение, когда будет результат».

Поток для продолжения:

  • может быть тем же
  • может быть другим
  • может вообще не быть явно заметным

Это деталь реализации, а не контракт.


Почему это ломает старое мышление

Раньше было просто:

  • один метод → один поток → одна последовательность.

Теперь:

  • метод — это машина состояний
  • выполнение может:
    • прерываться,
    • возобновляться,
    • продолжаться в другом контексте.

Поэтому:

  • lock + await — опасно,
  • блокирующие вызовы (.Result, .Wait()) — ловушка,
  • дедлоки стали тоньше и коварнее.

Асинхронность ≠ параллельность

Важно чётко разделять:

  • Асинхронность — не ждать, пока операция завершится,
  • Параллельность — делать несколько вещей одновременно.

async/await решает первую задачу.
Вторая решается:

  • Task.Run,
  • thread pool,
  • parallel APIs.

И смешивать их без понимания — прямой путь к проблемам.


Почем async/await так хорошо зашёл

Потому что он:

  • не ломает мышление разработчика,
  • выглядит как обычный код,
  • масштабируется лучше блокировок,
  • идеально ложится на I/O.

И при этом:

  • не обещает многопоточность,
  • не прячет сложность, а перекладывает её на runtime.

Итог

async/await — это не новая модель параллельности.
Это новый способ думать об ожидании.

Если раньше ожидание означало:

«Я занят, подожди»

То теперь:

«Я вернусь, когда будет смысл продолжать»

И именно поэтому асинхронный код:

  • легче масштабируется,
  • хуже отлаживается,
  • требует другого архитектурного мышления.

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *