Coroutine Builder — функції Kotlin Coroutines, які створюють і запускають корутини, визначаючи спосіб їх виконання. Білдери launch, async, runBlocking та produce покривають різні сценарії: від фонових завдань до паралельних обчислень із поверненням результату. За даними JetBrains, 2024, Coroutine Builder є основою корутинної моделі, забезпечуючи структуровану конкурентність та управління життєвим циклом.
Головне
Coroutine Builder — це функція-розширення Kotlin, яка приймає CoroutineScope та suspend-блок, створюючи та запускаючи нову корутину. Кожен білдер визначає, як корутина виконуватиметься: з поверненням результату чи без, з блокуванням потоку чи асинхронно. Білдери є точками входу в корутинну модель мови.
Всі білдери працюють через CoroutineScope, який керує життєвим циклом дочірніх корутин. При скасуванні scope всі запущені через нього корутини автоматично скасовуються — це принцип структурованої конкурентності. Даний підхід запобігає витокам корутин та гарантує передбачуване завершення.
import kotlinx.coroutines.*
fun main() = runBlocking {
// Білдери виконуються всередині CoroutineScope
val job = launch {
delay(1000L)
println("Світе!")
}
println("Привіт,")
job.join()
}
Kotlin надає чотири вбудованих білдери корутин: launch, async, runBlocking та produce. Кожен з них має свій тип, що повертається, та область застосування. Для мобільної розробки під Android основними є launch та async — вони працюють неблокуючим чином та інтегруються з архітектурними компонентами.
| Білдер | Тип, що повертається | Блокування потоку | Сценарій |
|---|---|---|---|
| launch | Job | Ні | Fire-and-forget завдання |
| async | Deferred<T> | Ні | Паралельні обчислення |
| runBlocking | T | Так | Тести, main-функція |
| produce | ReceiveChannel<E> | Ні | Потокова передача (застаріло) |
Кожен білдер приймає додаткові параметри: CoroutineStart (стратегія запуску), CoroutineContext (диспетчер, винятки) та іменований блок коду. За замовчуванням корутина запускається негайно (CoroutineStart.DEFAULT).
launch — найбільш використовуваний білдер в Android-розробці. Він запускає корутину, яка не повертає результат, та повертає об'єкт Job для керування її життєвим циклом. Це ідеальний вибір для операцій, де потрібен лише побічний ефект: збереження в БД, відправка аналітики, оновлення UI.
Білдер launch приймає CoroutineScope, необов'язковий CoroutineContext та suspend-блок. Повернений Job дозволяє скасувати корутину, дочекатися її завершення або перевірити статус.
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
val job: Job = scope.launch(CoroutineStart.LAZY) {
val data = fetchFromNetwork()
saveToDatabase(data)
}
job.start()
job.join()
Параметр CoroutineStart.LAZY відкладає запуск до явного виклику start() або join(). Це корисно для відкладеної ініціалізації та умовного запуску. Для стандартного негайного запуску використовується CoroutineStart.DEFAULT або параметр опускається.
async — білдер, що повертає Deferred<T> — асинхронну обіцянку результату. Виклик await() призупиняє корутину до отримання результату, не блокуючи потік. Це основний механізм для паралельних завдань у корутинах Kotlin.
async особливо ефективний, коли потрібно виконати кілька незалежних операцій одночасно. На відміну від послідовного виклику suspend-функцій, async запускає корутини паралельно, скорочуючи загальний час виконання.
suspend fun fetchUserData(): UserData {
val deferred1 = CoroutineScope(Dispatchers.IO).async { api.getProfile() }
val deferred2 = CoroutineScope(Dispatchers.IO).async { api.getSettings() }
val deferred3 = CoroutineScope(Dispatchers.IO).async { api.getNotifications() }
return UserData(
profile = deferred1.await(),
settings = deferred2.await(),
notifications = deferred3.await()
)
}
Deferred успадковується від Job, тому async підтримує всі операції життєвого циклу: скасування, очікування завершення, обробку винятків. При скасуванні scope дочірні Deferred корутини скасовуються автоматично.
runBlocking — єдиний білдер, який блокує поточний потік до завершення корутини. Він створює новий CoroutineScope та запускає передану корутину, блокуючи викликаючий потік. Використовується в точках входу main(), у тестах та при інтеграції з blocking-кодом.
runBlocking виправданий у трьох сценаріях: точка входу додатку (main), юніт-тести suspend-функцій та інтеграція з callback-бібліотеками, де не можна використовувати suspend. У production-коді Android використання runBlocking на головному потоці категорично не рекомендується.
class CoroutineTest {
@Test
fun `test suspend function`() = runBlocking {
val result = mySuspendFunction()
assertEquals("expected", result)
}
}
Для тестів рекомендується використовувати kotlinx-coroutines-test з TestCoroutineDispatcher замість runBlocking — це забезпечує контроль часу та уникає блокувань у тестовому середовищі.
Вибір Coroutine Builder залежить від результату, що повертається, та сценарію виконання. Якщо операція не потребує повернення даних — використовуйте launch. Якщо потрібен результат асинхронної операції — async. runBlocking застосовуйте лише для bridging, а produce замініть на Flow для реактивних потоків.
В Android-проектах з використанням Kotlin Coroutines основна пара білдерів — launch та async. launch використовується в ViewModel та UseCases для запуску корутин, а async — для паралельних запитів до мережі або БД. Сучасні бібліотеки (Ktor, Room) вже підтримують suspend-функції, що мінімізує необхідність у прямому використанні async.
Поширені запитання
launch повертає Job і не повертає результат виконання, а async повертає Deferred<T> — об'єкт, з якого можна отримати результат через await(). launch використовується для операцій fire-and-forget, async — для завдань, що повертають дані.
Не рекомендується. runBlocking на головному потоці викликає ANR та блокує UI. Використовуйте lifecycleScope.launch всередині Activity та Fragment — це вбудоване рішення без блокувань.
Білдер launch повертає об'єкт Job, який дозволяє контролювати життєвий цикл корутини: скасувати (cancel), дочекатися завершення (join), перевірити статус (isActive, isCompleted, isCancelled).
Deferred<T> — це асинхронна обіцянка результату, яку повертає білдер async. Він успадковується від Job та додає методи await() для отримання результату, getCompleted() для неблокуючого доступу та getCompletionExceptionOrNull() для перевірки винятку.
Використовуйте параметр CoroutineStart.LAZY: scope.launch(start = CoroutineStart.LAZY) { ... }. Потім викличте job.start() або job.join() для фактичного запуску. Це корисно для відкладеної ініціалізації та умовного виконання корутин.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також