Async/Await: суть, async функции и работа с await

Автор: IT Sectr Опубликовано: 2026-03-16 Время чтения: 8 мин

Async/Await — это пара ключевых слов для написания асинхронного кода в синхронном стиле, доступная в Swift с iOS 13 и в Kotlin с корутинами. По данным Apple Swift Documentation, 2026, Async/Await заменяет цепочки callback и GCD, делая асинхронный код линейным и читаемым. Ключевое слово async маркирует функцию как асинхронную, а await приостанавливает её выполнение до получения результата.

Главное

  • Async/Await — синтаксический сахар для асинхронного кода без вложенных callback
  • async маркирует функцию как асинхронную и приостанавливаемую
  • await приостанавливает выполнение функции до завершения асинхронной операции
  • Task в Swift создаёт новую асинхронную единицу работы с контекстом
  • Structured Concurrency гарантирует, что все дочерние задачи завершаются до родительской

Что такое 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/Await: приостановка и возобновление

Async функция компилируется в state machine — конечный автомат, который управляет состояниями выполнения. В точке await компилятор сохраняет контекст (локальные переменные, адрес возврата) и передаёт управление runtime. Когда результат готов, runtime восстанавливает контекст и продолжает выполнение.

В Swift эта state machine реализована на уровне компилятора SIL (Swift Intermediate Language). В Kotlin — через механизм Continuation Passing Style (CPS): каждая suspend функция принимает скрытый параметр Continuation, через который возвращается результат. Оба подхода гарантируют безопасное возобновление без утечек памяти.

  • Swift — компилятор транслирует async функцию в SIL с runAsync coroutine frame
  • Kotlin — каждая suspend функция получает Continuation и делегирует выполнение диспетчеру
  • Оба языка — освобождают поток при await, не блокируя его на время ожидания
  • Исключения — пробрасываются стандартными механизмами (throws / try-catch)

Async/Await vs Callbacks и GCD: сравнение подходов

Callback-подход требует передачи замыкания, которое вызывается после завершения операции. При трёх последовательных запросах получается три уровня вложенности с обработкой ошибок на каждом. Async/Await превращает те же три запроса в три последовательные строки с единым блоком catch.

GCD (DispatchQueue) решает проблему callback hell через serial очереди и DispatchGroup, но остаётся многословным. Для простой задачи — загрузить user, затем friends, затем UI — требуется создание очередей, групп и notify-блоков. Async/Await выполняет ту же задачу тремя строками в одной функции.

АспектCallbackGCDAsync/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 сценариев обработки потоков.

Swift Concurrency: Task, TaskGroup и Actor

Task — это основная единица асинхронной работы в Swift. Task создаётся в контексте существующего актора или на произвольном потоке. Внутри Task можно вызывать async функции через await. TaskGroup позволяет запускать несколько дочерних задач параллельно и собирать их результаты — аналог DispatchGroup на стероидах.

Actor — потокобезопасный reference type, защищающий своё состояние от гонок данных. Компилятор гарантирует, что доступ к свойствам actor возможен только через async вызовы или within actor isolation. Actor заменяет serial DispatchQueue для защиты shared mutable state без ручных блокировок.

Structured Concurrency

Принцип Structured Concurrency гласит: каждая асинхронная задача имеет родителя, и родитель не завершается, пока не завершатся все его дети. Если задача A запускает Task { await B() }, то A ожидает B. Это предотвращает утечку задач и обеспечивает предсказуемый lifecycle.

Async/Await в Kotlin с корутинами

В 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 и серверной части.

Примеры кода с Async/Await в Swift и Kotlin

Рассмотрим по два примера для каждого языка: последовательная загрузка данных и параллельные запросы с TaskGroup или async/await.

Swift: последовательные async вызовы

await приостанавливает выполнение до получения данных пользователя, затем — до получения друзей. Никаких вложенных замыканий.

swift
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)
}

Swift: параллельные запросы с TaskGroup

TaskGroup запускает задачи параллельно и собирает результаты в массив. Порядок результатов может не совпадать с порядком запуска.

swift
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
    }
}

Kotlin: последовательный вызов с async/await

suspend функции могут вызывать другие suspend функции. Аналог Swift: последовательные вызовы.

kotlin
suspend fun loadProfile(): Profile {
    val user = api.fetchUser()
    val friends = api.fetchFriends(user.id)
    return Profile(user, friends)
}

Kotlin: параллельные запросы с async/await

В Kotlin параллелизм достигается через coroutineScope с async для каждого запроса.

kotlin
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())
}

Типовые ошибки и ограничения Async/Await

Забытый Task { } — асинхронную функцию нельзя вызвать из синхронного контекста без обёртки в Task. В Swift это компилируется с ошибкой. В Kotlin требуется запуск через lifecycleScope или viewModelScope. Попытка вызвать suspend функцию из обычной функции не скомпилируется.

Избыточное использование async без необходимости

Не каждую функцию нужно делать асинхронной. CPU-интенсивные задачи без I/O не выигрывают от async — их лучше выполнять на DispatchQueue с .userInitiated QoS. Async/Await оптимизирован для I/O-bound операций: сеть, диск, ожидание.

Блокировка главного потока через .result

Использование .result на главном потоке (синхронное ожидание результата async функции) приводит к блокировке. В Swift Task.synchronousWait не рекомендуется Apple. В Kotlin runBlocking на главном потоке — антипаттерн, используйте lifecycleScope.

Утечка Task при отмене

Если Task не сохраняется в свойство класса, после выхода из контекста он отменяется. В Swift Task является scoped — его жизненный цикл привязан к создающему контексту. Сохраняйте Task в свойство при необходимости долгоживущей операции.

Часто задаваемые вопросы

Чем async/await отличается от DispatchQueue?

Async/Await — языковая конструкция, не требующая создания очередей. DispatchQueue — это системный API для управления потоками. Async/await компилируется в state machine на уровне языка, GCD — в syscall на уровне ОС.

Блокирует ли await поток?

Нет — await приостанавливает функцию, но освобождает поток для других задач. После завершения операции функция возобновляется на любом доступном потоке из пула.

Можно ли отменить async задачу?

Да — через Task.cancel() в Swift или Job.cancel() в Kotlin. Отмена распространяется на все дочерние задачи согласно Structured Concurrency. Код сам проверяет флаг отмены через Task.isCancelled или ensureActive().

Что такое MainActor в Swift?

MainActor — это actor, чьи свойства и методы всегда выполняются на главном потоке. Помечая функцию @MainActor, вы гарантируете, что UI-обновления происходят на правильном потоке, заменяя DispatchQueue.main.async.

В каких языках есть async/await?

Async/Await доступен в Swift (iOS 13+), Kotlin (через kotlinx.coroutines), Dart (Flutter), JavaScript/TypeScript, Python, C#, Rust и Go (через goroutines с синтаксическим сахаром).

Итоги

  • Async/Await — языковая конструкция для линейного асинхронного кода без callback hell
  • Swift Concurrency предоставляет Task, TaskGroup, Actor и Structured Concurrency
  • Kotlin coroutines используют suspend функции, Dispatchers и корутинные scope
  • Приостановка без блокировки — ключевое отличие async/await от потоков и очередей
  • Structured Concurrency гарантирует завершение всех дочерних задач до родительской
  • Async/Await vs GCD — первый предпочтительнее для нового кода, второй — для legacy и low-level
  • MainActor в Swift и Dispatchers.Main в Kotlin защищают UI-поток от фоновых операций

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также