runBlocking — Coroutine Builder у Kotlin, який блокує поточний потік до завершення переданої корутини. На відміну від launch та async, він не є suspend-функцією і може викликатися зі звичайного (blocking) коду. Згідно з документацією JetBrains, 2024, runBlocking слугує мостом між синхронним та асинхронним світами, дозволяючи запускати корутини з main-функції та тестів.
Головне
runBlocking — це функція Kotlin, яка створює новий CoroutineScope та запускає передану корутину, блокуючи поточний потік до її повного завершення. На відміну від усіх інших Coroutine Builder, runBlocking не є suspend-функцією і може викликатися зі звичайного синхронного коду. Сигнатура runBlocking приймає CoroutineContext та suspend-блок, повертаючи результат типу T.
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
runBlocking запускає новий event-loop у поточному потоці. Коли корутина викликає suspend-функцію (наприклад, delay() або await()), runBlocking блокує потік та виконує інші заплановані корутини в тому ж потоці до відновлення призупиненої. Це кооперативне блокування — потік не простоює, а обробляє інші корутини.
Внутрішній механізм runBlocking базується на event-loop: при виклику suspend-функції runBlocking призупиняє виконання поточного блоку та запускає інші корутини з черги. Коли suspend-функція завершується, виконання відновлюється. Цей цикл триває, поки всі корутини не завершаться.
runBlocking використовує власний однопотоковий пул для виконання корутин. На відміну від Dispatchers.IO або Default, runBlocking не перемикає потоки — він обробляє всі корутини в поточному потоці, чергуючи їх виконання. Це єдиний білдер, що гарантує виконання в тому ж потоці.
fun main() {
val threadName = Thread.currentThread().getName()
println("Перед runBlocking на $threadName")
val result = runBlocking {
println("Всередині runBlocking на ${Thread.currentThread().getName()}")
delay(500L)
"Done"
}
println("Після runBlocking: $result")
}
Вивід покаже, що всі три println виконуються на одному потоці. runBlocking не перемикає потік, а організовує кооперативну багатозадачність всередині одного потоку за допомогою event-loop.
runBlocking виправданий у трьох сценаріях: точка входу main() у консольних застосунках, юніт-тести suspend-функцій та міст — виклик suspend-коду з callback-орієнтованих або блокуючих бібліотек. У продакшн-коді Android використання на головному потоці категорично заборонено.
| Сценарій | Застосовність | Ризики |
|---|---|---|
| main() консольного застосунку | Так | Немає — це точка входу, потік не блокує UI |
| JUnit тести | Так | Мінімальні — тести синхронні за визначенням |
| Android UI-потік | Ні | ANR, лаги, зависання інтерфейсу |
| Callback → Coroutine | Так, з обережністю | Блокування пулу потоків при тривалих операціях |
Для Android-тестів використовуйте kotlinx-coroutines-test із TestDispatcher замість runBlocking. Це дає контроль над часом, автоматичне очищення та ізоляцію тестів.
У більшості сценаріїв runBlocking можна і потрібно замінити на асинхронні альтернативи. Для Android це viewModelScope, lifecycleScope або CoroutineScope з правильним диспетчером. Для тестів — TestCoroutineDispatcher та runTest.
// Погано: runBlocking на головному потоці Android
runBlocking(Dispatchers.Main) {
val result = networkApi.fetchData()
textView.setText(result)
}
// Добре: lifecycleScope
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
textView.setText(result)
}
Для тестів заміна — runTest з kotlinx-coroutines-test. Він створює TestCoroutineScope з віртуальним часом, дозволяючи тестувати затримки без реального очікування. Це прискорює тести та робить їх детермінованими.
Найчастіший сценарій — тестування suspend-функцій. runBlocking у тестах дозволяє синхронно дочекатися результату корутини без зміни архітектури. Другий сценарій — бібліотеки з callback API, де suspend-функції викликаються з blocking-контексту через runBlocking.
// Тестувати suspend функцію з runBlocking
class RepositoryTest {
@Test
fun `fetchUser returns correct data`() {
val repository = UserRepository(FakeApi())
val result = runBlocking {
repository.fetchUser("123")
}
assertEquals("John", result.name)
assertEquals("john@test.com", result.email)
}
}
Для мосту між callback та suspend-світом використовуйте CompletableDeferred у комбінації з runBlocking замість callbacks — це спрощує ланцюжки асинхронних операцій та підвищує читабельність коду.
Неправильне використання runBlocking — одна з частих помилок при переході з blocking-підходу на корутини. Основні проблеми: виклик на Main-потоці Android, вкладення runBlocking одне в одне, використання всередині асинхронних функцій та запуск тривалих операцій через runBlocking.
Золоте правило: runBlocking — це міст, а не заміна. Використовуйте його тільки для з'єднання blocking- та non-blocking-світів. Для всіх інших завдань застосовуйте launch, async або lifecycleScope.
Часто задавані питання
runBlocking — єдиний білдер, який не є suspend-функцією. Він запускає event-loop у поточному потоці і не повертає управління, поки всі корутини не завершені. launch та async повертають управління негайно, виконуючи корутину у фоні.
Не рекомендується. ViewModel має вбудований viewModelScope, який автоматично керує корутинами та скасовує їх при знищенні. runBlocking у ViewModel блокує потік і не реагує на скасування життєвого циклу.
Використовуйте runTest з бібліотеки kotlinx-coroutines-test. Він надає TestCoroutineScope з контролем віртуального часу, автоматичним скасуванням та детермінованим виконанням.
Event-loop — цикл обробки подій всередині runBlocking. Коли корутина призупиняється (наприклад, delay()), event-loop перемикається на виконання інших готових корутин у тому ж потоці. Це створює ілюзію багатозадачності без перемикання потоків.
Вкладений runBlocking в одному потоці створює deadlock — зовнішній блок очікує внутрішній, але внутрішній не може початися, поки зовнішній не завершиться. В різних потоках це допустимо, але вкрай не рекомендується через складність налагодження.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також