Coroutines — ключевые понятия, Job и Dispatchers в Kotlin

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

Coroutines (корутины) — это лёгкие потоки Kotlin для асинхронного программирования, доступные через библиотеку kotlinx.coroutines. По данным JetBrains Kotlin Documentation, 2026, Coroutines позволяют приостанавливать выполнение функции без блокировки потока, в отличие от традиционных Thread. Корутины запускаются на ограниченном пуле потоков, что делает их в тысячу раз легче нативных потоков. Kotlin Coroutines полностью интегрированы с Android Jetpack, Retrofit, Room и другими популярными библиотеками экосистемы Android.

Главное

  • Coroutines — лёгкие потоки Kotlin для асинхронного кода без блокировки
  • suspend функция — функция, способная приостанавливаться и возобновляться без блокировки потока
  • Dispatcher определяет пул потоков для выполнения корутины
  • Job — дескриптор корутины с поддержкой отмены и отслеживания состояния
  • CoroutineScope управляет жизненным циклом корутин и их отменой при завершении

Что такое корутины Kotlin

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 функции: приостановка и возобновление

suspend — ключевое слово Kotlin, маркирующее функцию как приостанавливаемую. Такую функцию можно вызывать только из другой suspend функции или из корутины. Внутри suspend функции можно вызывать другие suspend функции в любом порядке, и каждая точка вызова — потенциальная точка приостановки.

Механика проста: когда suspend функция вызывает другую suspend функцию, она приостанавливается в этой точке, освобождая поток. После завершения вызванной функции runtime продолжает выполнение с сохранённого места. Это называется cooperative cancellation — никакие потоки не блокируются.

  • Приостановка — корутина освобождает поток, не блокируя его
  • Возобновление — корутина продолжает с того же места, где приостановилась
  • Поток — корутина может приостановиться на потоке A и возобновиться на потоке B
  • Исключения — обрабатываются через try/catch так же, как в синхронном коде

Важно: suspend функция не является асинхронной по умолчанию. Порядок выполнения остаётся последовательным, если не используются launch или async. suspend лишь позволяет функции быть приостановленной, не блокируя поток, и быть частью корутинного контекста. Continuation Passing Style — это модель компиляции, при которой каждая suspend функция получает скрытый колбек Continuation, а компилятор генерирует конечный автомат для управления приостановками и возобновлениями.

CoroutineScope и Structured Concurrency

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-задачНе отменяется автоматически
viewModelScopeAndroid ViewModelПри очистке ViewModel
lifecycleScopeAndroid Activity/FragmentПри уничтожении lifecycle
coroutineScopeВнутри suspend функцииПри отмене родительской job

SupervisorJob для обработки ошибок

Обычный Job при ошибке одной дочерней корутины отменяет всех siblings. SupervisorJob — исключение: сбой в одной дочерней корутине не влияет на остальные. Это важно, когда несколько независимых задач выполняются параллельно, и одна из них может упасть без необходимости отменять другие.

Dispatchers и корутинные строители

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 — только для тестов или точек входа.

Примеры кода с корутинами в Kotlin

Рассмотрим три практических сценария: базовая корутина с launch, параллельный вызов с async и обработка ошибок с SupervisorJob.

Запуск корутины с launch

viewModelScope.launch запускает корутину в контексте ViewModel. При очистке ViewModel корутина автоматически отменяется.

kotlin
class ProfileViewModel : ViewModel() {
    fun loadUser() {
        viewModelScope.launch(Dispatchers.IO) {
            val user = api.fetchUser()
            withContext(Dispatchers.Main) {
                showUser(user)
            }
        }
    }
}

Параллельные запросы с async

coroutineScope с async запускает три запроса параллельно. Результаты собираются через .await(). Если любой из запросов падает, отменяются все.

kotlin
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

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

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

Корутины vs потоки: сравнение и сценарии

Потоки (Thread) — примитив операционной системы. Каждый поток имеет собственный стек (~1 МБ) и требует системного вызова для создания и переключения. Корутины — примитив языка, не привязанный к ОС. Они используют Continuation для сохранения состояния и переключаются на уровне рантайма без системных вызовов.

  • Память — поток ~1 МБ, корутина ~100 байт. Разница в 10 000 раз
  • Создание — поток ~1 мкс syscall, корутина ~0.01 мкс на уровне JVM
  • Переключение — поток ~0.1 мкс (syscall), корутина ~0.001 мкс (continuation)
  • Максимум — тысяч потоков vs миллионов корутин на устройстве
  • Отмена — поток нельзя отменить извне (deprecated Thread.stop), корутину можно через Job.cancel()

По данным 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 и чем он отличается от Default?

Dispatchers.IO предназначен для блокирующих I/O операций (сеть, файлы) и может создавать новые потоки при необходимости. Dispatchers.Default фиксирован размером пулом (количество ядер процессора) для CPU-интенсивных вычислений.

Как отменить запущенную корутину?

Job.cancel() отменяет корутину и всех её детей. Для проверки отмены внутри корутины используйте ensureActive() — он выбрасывает CancellationException, если корутина отменена.

Можно ли использовать корутины с RxJava?

Да — через библиотеку kotlinx-coroutines-rx3. Она предоставляет функции awaitSingle, awaitFirst и другие для преобразования Observable/Single в suspend функции и обратно через flowable.

Что такое Flow в корутинах?

Flow — холодный асинхронный поток данных, аналог RxJava Observable для корутин. Flow эмиттит значения последовательно и завершается с исключением или успехом. Поддерживает map, filter, catch и другие операторы.

Итоги

  • Coroutines — лёгкие потоки Kotlin с приостановкой без блокировки через Continuation Passing Style
  • suspend — ключевое слово для маркировки приостанавливаемых функций
  • Dispatchers управляют пулом потоков: Main, IO, Default соответственно
  • CoroutineScope привязывает жизненный цикл корутин к компоненту (Activity, ViewModel)
  • launch запускает корутину без результата, async/await — с результатом
  • Structured Concurrency гарантирует иерархическую отмену дочерних корутин
  • Корутины vs потоки — корутины в 10 000 раз легче и являются стандартом для Android

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

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

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

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