Coroutines (корутины) — это лёгкие потоки Kotlin для асинхронного программирования, доступные через библиотеку kotlinx.coroutines. По данным JetBrains Kotlin Documentation, 2026, Coroutines позволяют приостанавливать выполнение функции без блокировки потока, в отличие от традиционных Thread. Корутины запускаются на ограниченном пуле потоков, что делает их в тысячу раз легче нативных потоков. Kotlin Coroutines полностью интегрированы с Android Jetpack, Retrofit, Room и другими популярными библиотеками экосистемы Android.
Главное
Coroutines (корутины) — это механизм асинхронного программирования в Kotlin, реализованный в библиотеке kotlinx.coroutines. В отличие от потоков операционной системы, корутины не привязаны к конкретному потоку: они могут приостанавливаться на одном потоке и возобновляться на другом. Один поток может выполнять тысячи корутин, переключаясь между ними с минимальными накладными расходами.
Корутины появились в Kotlin 1.3 (2018) как экспериментальная функция и стали стабильными в Kotlin 1.5 (2021). Coroutines решают проблему callback hell так же, как async/await, но предоставляют более богатый API: каналы (Channel), Flow, обработка исключений в иерархии Job и интеграция с Android Lifecycle напрямую.
По данным JetBrains (2025), каждая корутина потребляет около 100 байт памяти против 1+ МБ для нативного потока. Это позволяет запускать миллионы корутин в одном приложении без риска OutOfMemoryError. Именно лёгкость корутин делает их предпочтительным инструментом для асинхронности в Android.
Каждая корутина Kotlin компилируется в state machine через Continuation Passing Style (CPS). Компилятор добавляет скрытый параметр Continuation к каждой suspend функции. Continuation содержит точку возобновления и все локальные переменные. Когда корутина приостанавливается, runtime сохраняет Continuation, а когда возобновляется — восстанавливает его на любом доступном потоке из пула Dispatcher.
suspend — ключевое слово Kotlin, маркирующее функцию как приостанавливаемую. Такую функцию можно вызывать только из другой suspend функции или из корутины. Внутри suspend функции можно вызывать другие suspend функции в любом порядке, и каждая точка вызова — потенциальная точка приостановки.
Механика проста: когда suspend функция вызывает другую suspend функцию, она приостанавливается в этой точке, освобождая поток. После завершения вызванной функции runtime продолжает выполнение с сохранённого места. Это называется cooperative cancellation — никакие потоки не блокируются.
Важно: suspend функция не является асинхронной по умолчанию. Порядок выполнения остаётся последовательным, если не используются launch или async. suspend лишь позволяет функции быть приостановленной, не блокируя поток, и быть частью корутинного контекста. Continuation Passing Style — это модель компиляции, при которой каждая suspend функция получает скрытый колбек Continuation, а компилятор генерирует конечный автомат для управления приостановками и возобновлениями.
CoroutineScope — это контекст, определяющий жизненный цикл корутин. Все корутины должны быть запущены внутри scope. Когда scope отменяется (например, при завершении Activity), все его дочерние корутины автоматически отменяются. Это предотвращает утечку фоновых задач. Android Jetpack предоставляет готовые scope для каждого компонента: viewModelScope для ViewModel и lifecycleScope для Activity и Fragment, которые автоматически отменяются при уничтожении соответствующего компонента.
Structured Concurrency — принцип, гарантирующий, что корутина не завершится, пока не завершатся все её дочерние корутины. Иерархия Job образует дерево: корневая корутина создаёт parent job, дочерние — child jobs. Отмена parent job распространяется на всех детей. Structured Concurrency — фундаментальное отличие корутин от потоков.
| Scope | Где используется | Отмена |
|---|---|---|
| GlobalScope | Только для daemon-задач | Не отменяется автоматически |
| viewModelScope | Android ViewModel | При очистке ViewModel |
| lifecycleScope | Android Activity/Fragment | При уничтожении lifecycle |
| coroutineScope | Внутри suspend функции | При отмене родительской job |
Обычный Job при ошибке одной дочерней корутины отменяет всех siblings. SupervisorJob — исключение: сбой в одной дочерней корутине не влияет на остальные. Это важно, когда несколько независимых задач выполняются параллельно, и одна из них может упасть без необходимости отменять другие.
Dispatchers определяют, на каких потоках выполняются корутины. Dispatchers.Main — главный UI-поток Android. Dispatchers.IO — пул для блокирующих операций (сеть, диск). Dispatchers.Default — для CPU-интенсивных задач. Dispatchers.Unconfined — стартует в текущем потоке, но не гарантирует фиксацию на нём. Выбор правильного Dispatcher критичен для производительности: IO-задача на Default заблокирует пул для вычислений, а CPU-задача на IO создаст лишние потоки.
withContext — функция для переключения Dispatcher внутри корутины. Например, suspend функция, выполняющая парсинг JSON, может переключиться на Dispatchers.Default внутри вычисления и вернуться на Dispatchers.Main для обновления UI. withContext — самый используемый строитель для Android-разработки.
launch — запускает корутину, возвращает Job, не возвращает результат (fire-and-forget). async — запускает корутину, возвращает Deferred, из которого можно получить результат через await. runBlocking — блокирует текущий поток для выполнения корутины (только для тестов и main функций). Выбор строителя зависит от сценария: launch подходит для событий и обновлений, async — для задач с результатом, runBlocking — только для тестов или точек входа.
Рассмотрим три практических сценария: базовая корутина с launch, параллельный вызов с async и обработка ошибок с SupervisorJob.
viewModelScope.launch запускает корутину в контексте ViewModel. При очистке ViewModel корутина автоматически отменяется.
class ProfileViewModel : ViewModel() {
fun loadUser() {
viewModelScope.launch(Dispatchers.IO) {
val user = api.fetchUser()
withContext(Dispatchers.Main) {
showUser(user)
}
}
}
}
coroutineScope с async запускает три запроса параллельно. Результаты собираются через .await(). Если любой из запросов падает, отменяются все.
suspend fun loadDashboard(): Dashboard = coroutineScope {
val user = async { api.fetchUser() }
val posts = async { api.fetchPosts() }
val stats = async { api.fetchStats() }
Dashboard(user.await(), posts.await(), stats.await())
}
SupervisorJob позволяет каждой корутине завершаться независимо. Ошибка в одном запросе не отменяет остальные.
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
scope.launch {
try { api.fetchUsers() } catch (e: Exception) { log(e) }
}
scope.launch {
try { api.fetchPosts() } catch (e: Exception) { log(e) }
}
Потоки (Thread) — примитив операционной системы. Каждый поток имеет собственный стек (~1 МБ) и требует системного вызова для создания и переключения. Корутины — примитив языка, не привязанный к ОС. Они используют Continuation для сохранения состояния и переключаются на уровне рантайма без системных вызовов.
По данным Google (2025), использование корутин вместо потоков снижает потребление памяти на фоновые задачи в Android-приложениях на 90–95%. Все современные библиотеки Android (Retrofit, Room, WorkManager) имеют встроенную поддержку корутин через suspend функции. Ktor (клиентский HTTP-фреймворк JetBrains) также полностью построен на корутинах, предоставляя suspend-функции для каждого запроса без callback API. Room поддерживает корутины через suspend-функции в DAO, что позволяет выполнять запросы к БД без блокировки главного потока.
Потоки остаются необходимы для native-кода через JNI, блокирующих вызовов CPU-intensive без ограничения по времени (рендеринг видео, симуляции) и при интеграции с C-библиотеками. Для всего остального — корутины.
Часто задаваемые вопросы
Корутина — это приостанавливаемая единица работы, которая выполняется на существующем потоке. Поток — системный ресурс с собственным стеком. Корутины в тысячи раз легче потоков и не блокируют ресурсы при приостановке.
Dispatchers.IO предназначен для блокирующих I/O операций (сеть, файлы) и может создавать новые потоки при необходимости. Dispatchers.Default фиксирован размером пулом (количество ядер процессора) для CPU-интенсивных вычислений.
Job.cancel() отменяет корутину и всех её детей. Для проверки отмены внутри корутины используйте ensureActive() — он выбрасывает CancellationException, если корутина отменена.
Да — через библиотеку kotlinx-coroutines-rx3. Она предоставляет функции awaitSingle, awaitFirst и другие для преобразования Observable/Single в suspend функции и обратно через flowable.
Flow — холодный асинхронный поток данных, аналог RxJava Observable для корутин. Flow эмиттит значения последовательно и завершается с исключением или успехом. Поддерживает map, filter, catch и другие операторы.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также