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 мільйони корутин на пристрої
  • Скасування — потік не можна скасувати ззовні (застарілий 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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