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