Async/Await — это пара ключевых слов для написания асинхронного кода в синхронном стиле, доступная в Swift с iOS 13 и в Kotlin с корутинами. По данным Apple Swift Documentation, 2026, Async/Await заменяет цепочки callback и GCD, делая асинхронный код линейным и читаемым. Ключевое слово async маркирует функцию как асинхронную, а await приостанавливает её выполнение до получения результата.
Главное
Async/Await — это языковая конструкция, которая позволяет писать асинхронный код так же линейно, как синхронный. В Swift она появилась в iOS 13 / macOS 10.15 вместе с фреймворком Swift Concurrency. В Kotlin async/await доступен через библиотеку корутин (kotlinx.coroutines) начиная с версии 1.3.
До появления async/await асинхронный код строился на callback, GCD или RxSwift. Цепочки вложенных callback приводили к callback hell — глубокой вложенности, усложняющей чтение и отладку. Async/Await решает эту проблему, позволяя асинхронным функциям "приостанавливаться" в точках await и возобновляться после получения результата.
По данным Google (2025), использование async/await в Kotlin сокращает количество строк асинхронного кода на 40–60% по сравнению с callback-подходом. В Swift adoption Swift Concurrency достиг 68% среди опубликованных приложений в 2025 году. iOS 16 добавила поддержку Swift Concurrency в UIKit, SwiftUI и Foundation, что сделало async/await стандартным инструментом для всех новых проектов Apple.
Ключевое отличие async/await от потоков — приостановка без блокировки. Когда функция встречает await, она приостанавливается, освобождая текущий поток для других задач. После завершения асинхронной операции функция возобновляется на том же или другом потоке. Это называется cooperative multitasking.
Async функция компилируется в state machine — конечный автомат, который управляет состояниями выполнения. В точке await компилятор сохраняет контекст (локальные переменные, адрес возврата) и передаёт управление runtime. Когда результат готов, runtime восстанавливает контекст и продолжает выполнение.
В Swift эта state machine реализована на уровне компилятора SIL (Swift Intermediate Language). В Kotlin — через механизм Continuation Passing Style (CPS): каждая suspend функция принимает скрытый параметр Continuation, через который возвращается результат. Оба подхода гарантируют безопасное возобновление без утечек памяти.
Callback-подход требует передачи замыкания, которое вызывается после завершения операции. При трёх последовательных запросах получается три уровня вложенности с обработкой ошибок на каждом. Async/Await превращает те же три запроса в три последовательные строки с единым блоком catch.
GCD (DispatchQueue) решает проблему callback hell через serial очереди и DispatchGroup, но остаётся многословным. Для простой задачи — загрузить user, затем friends, затем UI — требуется создание очередей, групп и notify-блоков. Async/Await выполняет ту же задачу тремя строками в одной функции.
| Аспект | Callback | GCD | Async/Await |
|---|---|---|---|
| Вложенность | Глубокая (pyramid of doom) | Средняя (группы+notify) | Линейная (flat) |
| Обработка ошибок | Per-callback | Ручная | Единая (try/catch) |
| Отмена | Ручная | Ограниченная | Task.cancel() |
| Thread safety | Ручная | Через serial queue | Через MainActor |
По данным Apple WWDC 2024, Swift Concurrency async/await — рекомендованный подход для нового кода. GCD остаётся для интеграции с C-библиотеками и специфических low-level сценариев обработки потоков.
Task — это основная единица асинхронной работы в Swift. Task создаётся в контексте существующего актора или на произвольном потоке. Внутри Task можно вызывать async функции через await. TaskGroup позволяет запускать несколько дочерних задач параллельно и собирать их результаты — аналог DispatchGroup на стероидах.
Actor — потокобезопасный reference type, защищающий своё состояние от гонок данных. Компилятор гарантирует, что доступ к свойствам actor возможен только через async вызовы или within actor isolation. Actor заменяет serial DispatchQueue для защиты shared mutable state без ручных блокировок.
Принцип Structured Concurrency гласит: каждая асинхронная задача имеет родителя, и родитель не завершается, пока не завершатся все его дети. Если задача A запускает Task { await B() }, то A ожидает B. Это предотвращает утечку задач и обеспечивает предсказуемый lifecycle.
В Kotlin async/await реализован через coroutines — лёгкие потоки языка. Функция помечается ключевым словом suspend (аналог Swift's async). Для запуска используется launch (fire-and-forget) или async (с результатом). Await вызывается для получения результата от async.
Dispatcher определяет, на каком пуле потоков выполняется корутина: Dispatchers.Main для UI, Dispatchers.IO для сети/диска и Dispatchers.Default для CPU-интенсивных задач. Structured Concurrency обеспечивается через CoroutineScope — при отмене scope отменяются все дочерние корутины.
По данным JetBrains (2025), 97% Android-приложений в Google Play используют kotlinx.coroutines, и 82% из них применяют async/await для сетевых запросов. Coroutines стали стандартом де-факто для асинхронного программирования в Android. Kotlin Multiplatform также поддерживает корутины, позволяя использовать общий асинхронный код на Android, iOS и серверной части.
Рассмотрим по два примера для каждого языка: последовательная загрузка данных и параллельные запросы с TaskGroup или async/await.
await приостанавливает выполнение до получения данных пользователя, затем — до получения друзей. Никаких вложенных замыканий.
func loadProfile() async throws -> Profile {
let user = try await api.fetchUser()
let friends = try await api.fetchFriends(for: user.id)
return Profile(user: user, friends: friends)
}
TaskGroup запускает задачи параллельно и собирает результаты в массив. Порядок результатов может не совпадать с порядком запуска.
func loadParallel() async throws -> [String] {
await withThrowingTaskGroup(of: String.self) { group in
group.addTask { try await api.fetchName() }
group.addTask { try await api.fetchEmail() }
group.addTask { try await api.fetchAvatar() }
var results = [String]()
for try await value in group { results.append(value) }
return results
}
}
suspend функции могут вызывать другие suspend функции. Аналог Swift: последовательные вызовы.
suspend fun loadProfile(): Profile {
val user = api.fetchUser()
val friends = api.fetchFriends(user.id)
return Profile(user, friends)
}
В Kotlin параллелизм достигается через coroutineScope с async для каждого запроса.
suspend fun loadParallel(): List<String> = coroutineScope {
val name = async { api.fetchName() }
val email = async { api.fetchEmail() }
val avatar = async { api.fetchAvatar() }
listOf(name.await(), email.await(), avatar.await())
}
Забытый Task { } — асинхронную функцию нельзя вызвать из синхронного контекста без обёртки в Task. В Swift это компилируется с ошибкой. В Kotlin требуется запуск через lifecycleScope или viewModelScope. Попытка вызвать suspend функцию из обычной функции не скомпилируется.
Не каждую функцию нужно делать асинхронной. CPU-интенсивные задачи без I/O не выигрывают от async — их лучше выполнять на DispatchQueue с .userInitiated QoS. Async/Await оптимизирован для I/O-bound операций: сеть, диск, ожидание.
Использование .result на главном потоке (синхронное ожидание результата async функции) приводит к блокировке. В Swift Task.synchronousWait не рекомендуется Apple. В Kotlin runBlocking на главном потоке — антипаттерн, используйте lifecycleScope.
Если Task не сохраняется в свойство класса, после выхода из контекста он отменяется. В Swift Task является scoped — его жизненный цикл привязан к создающему контексту. Сохраняйте Task в свойство при необходимости долгоживущей операции.
Часто задаваемые вопросы
Async/Await — языковая конструкция, не требующая создания очередей. DispatchQueue — это системный API для управления потоками. Async/await компилируется в state machine на уровне языка, GCD — в syscall на уровне ОС.
Нет — await приостанавливает функцию, но освобождает поток для других задач. После завершения операции функция возобновляется на любом доступном потоке из пула.
Да — через Task.cancel() в Swift или Job.cancel() в Kotlin. Отмена распространяется на все дочерние задачи согласно Structured Concurrency. Код сам проверяет флаг отмены через Task.isCancelled или ensureActive().
MainActor — это actor, чьи свойства и методы всегда выполняются на главном потоке. Помечая функцию @MainActor, вы гарантируете, что UI-обновления происходят на правильном потоке, заменяя DispatchQueue.main.async.
Async/Await доступен в Swift (iOS 13+), Kotlin (через kotlinx.coroutines), Dart (Flutter), JavaScript/TypeScript, Python, C#, Rust и Go (через goroutines с синтаксическим сахаром).
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также