runBlocking — що це таке, блокуючий міст і як працює

Автор: IT Sectr Опубліковано: 2026-06-22 Час читання: 7 хв

runBlocking — Coroutine Builder у Kotlin, який блокує поточний потік до завершення переданої корутини. На відміну від launch та async, він не є suspend-функцією і може викликатися зі звичайного (blocking) коду. Згідно з документацією JetBrains, 2024, runBlocking слугує мостом між синхронним та асинхронним світами, дозволяючи запускати корутини з main-функції та тестів.

Головне

  • runBlocking — блокуючий білдер, який створює CoroutineScope та очікує завершення корутини
  • Блокування потоку — runBlocking утримує поточний потік до повного завершення корутини та всіх дочірніх
  • Точки входу — main(), JUnit-тести та міст між blocking та async кодом
  • Заборонено на Main-потоці Android — виклик runBlocking у UI-потоці викликає ANR
  • Альтернативи — lifecycleScope, viewModelScope, TestCoroutineDispatcher для Android

Що таке runBlocking?

runBlocking — це функція Kotlin, яка створює новий CoroutineScope та запускає передану корутину, блокуючи поточний потік до її повного завершення. На відміну від усіх інших Coroutine Builder, runBlocking не є suspend-функцією і може викликатися зі звичайного синхронного коду. Сигнатура runBlocking приймає CoroutineContext та suspend-блок, повертаючи результат типу T.

kotlin
public fun <T> runBlocking(
    context: CoroutineContext = EmptyCoroutineContext,
    block: suspend CoroutineScope.() -> T
): T

runBlocking запускає новий event-loop у поточному потоці. Коли корутина викликає suspend-функцію (наприклад, delay() або await()), runBlocking блокує потік та виконує інші заплановані корутини в тому ж потоці до відновлення призупиненої. Це кооперативне блокування — потік не простоює, а обробляє інші корутини.

Як працює runBlocking

Внутрішній механізм runBlocking базується на event-loop: при виклику suspend-функції runBlocking призупиняє виконання поточного блоку та запускає інші корутини з черги. Коли suspend-функція завершується, виконання відновлюється. Цей цикл триває, поки всі корутини не завершаться.

Event-loop під капотом

runBlocking використовує власний однопотоковий пул для виконання корутин. На відміну від Dispatchers.IO або Default, runBlocking не перемикає потоки — він обробляє всі корутини в поточному потоці, чергуючи їх виконання. Це єдиний білдер, що гарантує виконання в тому ж потоці.

kotlin
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

runBlocking виправданий у трьох сценаріях: точка входу main() у консольних застосунках, юніт-тести suspend-функцій та міст — виклик suspend-коду з callback-орієнтованих або блокуючих бібліотек. У продакшн-коді Android використання на головному потоці категорично заборонено.

СценарійЗастосовністьРизики
main() консольного застосункуТакНемає — це точка входу, потік не блокує UI
JUnit тестиТакМінімальні — тести синхронні за визначенням
Android UI-потікНіANR, лаги, зависання інтерфейсу
Callback → CoroutineТак, з обережністюБлокування пулу потоків при тривалих операціях

Для Android-тестів використовуйте kotlinx-coroutines-test із TestDispatcher замість runBlocking. Це дає контроль над часом, автоматичне очищення та ізоляцію тестів.

Альтернативи runBlocking

У більшості сценаріїв runBlocking можна і потрібно замінити на асинхронні альтернативи. Для Android це viewModelScope, lifecycleScope або CoroutineScope з правильним диспетчером. Для тестів — TestCoroutineDispatcher та runTest.

kotlin
    // Погано: 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 з віртуальним часом, дозволяючи тестувати затримки без реального очікування. Це прискорює тести та робить їх детермінованими.

Приклади використання runBlocking

Найчастіший сценарій — тестування suspend-функцій. runBlocking у тестах дозволяє синхронно дочекатися результату корутини без зміни архітектури. Другий сценарій — бібліотеки з callback API, де suspend-функції викликаються з blocking-контексту через runBlocking.

kotlin
// Тестувати 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.

  • ANR — runBlocking на Main-потоці Android блокує відтворення UI більш ніж на 5 секунд
  • Deadlock — вкладений runBlocking всередині корутини на тому ж потоці призводить до взаємного блокування
  • Плутанина з диспетчерами — Dispatchers.Main всередині runBlocking у background-потоці не має Looper і падає з винятком
  • Витоки пам'яті — runBlocking не скасовується автоматично при знищенні Activity/Fragment

Золоте правило: runBlocking — це міст, а не заміна. Використовуйте його тільки для з'єднання blocking- та non-blocking-світів. Для всіх інших завдань застосовуйте launch, async або lifecycleScope.

Часто задавані питання

Чому runBlocking блокує потік, а інші білдери — ні?

runBlocking — єдиний білдер, який не є suspend-функцією. Він запускає event-loop у поточному потоці і не повертає управління, поки всі корутини не завершені. launch та async повертають управління негайно, виконуючи корутину у фоні.

Чи можна використовувати runBlocking в Android ViewModel?

Не рекомендується. ViewModel має вбудований viewModelScope, який автоматично керує корутинами та скасовує їх при знищенні. runBlocking у ViewModel блокує потік і не реагує на скасування життєвого циклу.

Чим замінити runBlocking в юніт-тестах?

Використовуйте runTest з бібліотеки kotlinx-coroutines-test. Він надає TestCoroutineScope з контролем віртуального часу, автоматичним скасуванням та детермінованим виконанням.

Що таке event-loop в runBlocking?

Event-loop — цикл обробки подій всередині runBlocking. Коли корутина призупиняється (наприклад, delay()), event-loop перемикається на виконання інших готових корутин у тому ж потоці. Це створює ілюзію багатозадачності без перемикання потоків.

Що станеться при виклику runBlocking всередині runBlocking?

Вкладений runBlocking в одному потоці створює deadlock — зовнішній блок очікує внутрішній, але внутрішній не може початися, поки зовнішній не завершиться. В різних потоках це допустимо, але вкрай не рекомендується через складність налагодження.

Підсумки

  • runBlocking — блокуючий Coroutine Builder, міст між blocking та асинхронним кодом
  • Event-loop runBlocking обробляє корутини кооперативно в одному потоці без перемикання
  • Дозволені сценарії — main(), JUnit-тести, міст з callback-бібліотек
  • Заборонені сценарії — Android UI-потік, вкладені виклики, тривалі операції
  • Альтернативи — lifecycleScope, viewModelScope, runTest для тестів
  • ANR-ризик — runBlocking на Main-потоці викликає зависання застосунку після 5 секунд
  • Для продакшн-коду Android використовуйте асинхронні білдери — runBlocking не призначений для UI

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

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

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