Думаю, не ошибусь, если скажу, что специалисты разного возраста говорят о разном, когда упоминают многопоточность в .NET. Те, кто начинал работу с .NET давно, мыслили в терминах «потоков». Те, кто уже начали с async/await, часто понимают под этим просто управление ожиданием, не связанное с управлением потоками…
Давайте вспомним разные эпизоды истории синхронизации.
Как мы ждали раньше: блокировка как норма
В раннем .NET (* и не только в нём, я помню очень большой проект на C++ с той же парадигмой многопоточности) ожидание выглядело просто:
- поток зашёл в метод
- метод делает работу
- поток ждёт, пока работа завершится
Если операция была долгой (I/O, сеть, диск):
- поток блокировался
- ресурсы простаивали
- UI зависал
- сервер «съедал» потоки
Типичные инструменты того времени:
lockMonitorWaitHandleThread.Sleep- ручное управление потоками
Ожидание = занятый поток.
Асинхронность до async/await: боль и колбэки
Когда стало понятно, что блокировать потоки дорого, появились асинхронные API:
- конструкция с вызовами begin и end (Begin/End pattern),
- callbacks,
- события,
IAsyncResult.
Примерно так:
- операция стартует,
- управление возвращается сразу,
- при завершении вызывается callback.
Технически — эффективно.
Практически — нечитаемо и хрупко.
Код:
- терял линейность,
- размазывался по методам,
- усложнял обработку ошибок.
Что изменил async/await
async/await не добавил новых возможностей, он:
- переписал асинхронность в нормальный синтаксис
- сохранил линейное мышление
- убрал ручное управление продолжениями
Ключевая идея:
Поток не ждёт. Ждёт состояние операции.
Когда выполнение доходит до await:
- если результат готов — код идёт дальше
- если нет — метод приостанавливается
- поток освобождается
- продолжение выполняется позже
Где здесь потоки — а где нет
Это самый важный момент.
awaitне создаёт потокawaitне гарантирует другой потокawaitне означает параллельность
Он означает только одно:
«Продолжи выполнение, когда будет результат».
Поток для продолжения:
- может быть тем же
- может быть другим
- может вообще не быть явно заметным
Это деталь реализации, а не контракт.
Почему это ломает старое мышление
Раньше было просто:
- один метод → один поток → одна последовательность.
Теперь:
- метод — это машина состояний
- выполнение может:
- прерываться,
- возобновляться,
- продолжаться в другом контексте.
Поэтому:
lock+await— опасно,- блокирующие вызовы (
.Result,.Wait()) — ловушка, - дедлоки стали тоньше и коварнее.
Асинхронность ≠ параллельность
Важно чётко разделять:
- Асинхронность — не ждать, пока операция завершится,
- Параллельность — делать несколько вещей одновременно.
async/await решает первую задачу.
Вторая решается:
Task.Run,- thread pool,
- parallel APIs.
И смешивать их без понимания — прямой путь к проблемам.
Почем async/await так хорошо зашёл
Потому что он:
- не ломает мышление разработчика,
- выглядит как обычный код,
- масштабируется лучше блокировок,
- идеально ложится на I/O.
И при этом:
- не обещает многопоточность,
- не прячет сложность, а перекладывает её на runtime.
Итог
async/await — это не новая модель параллельности.
Это новый способ думать об ожидании.
Если раньше ожидание означало:
«Я занят, подожди»
То теперь:
«Я вернусь, когда будет смысл продолжать»
И именно поэтому асинхронный код:
- легче масштабируется,
- хуже отлаживается,
- требует другого архитектурного мышления.