runBlocking — что это такое, блокирующий мост и как работает

Автор: IT Sectr Опубликовано: 2026-06-22 Время чтения: 7 мин

runBlocking — Coroutine Builder в Kotlin, который блокирует текущий поток до завершения переданной сопрограммы. В отличие от launch и async, он не является suspend-функцией и может вызываться из обычного (blocking) кода. По данным документации JetBrains, 2024, runBlocking служит мостом между синхронным и асинхронным миром, позволяя запускать корутины из main-функции и тестов.

Главное

  • runBlocking — блокирующий билдер, создающий CoroutineScope и ожидающий завершения корутины
  • Блокировка потока — runBlocking удерживает текущий поток до полного завершения корутины и всех дочерних
  • Точки входа — main(), JUnit-тесты и bridging между 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("Before runBlocking on $threadName")

    val result = runBlocking {
        println("Inside runBlocking on ${Thread.currentThread().getName()}")
        delay(500L)
        "Done"
    }

    println("After runBlocking: $result")
}

Вывод покажет, что все три println выполняются на одном потоке. runBlocking не переключает поток, а организует кооперативную многозадачность внутри одного потока с помощью event-loop.

Когда использовать runBlocking

runBlocking оправдан в трёх сценариях: точка входа main() в консольных приложениях, юнит-тесты suspend-функций и bridging — вызов suspend-кода из callback-based или blocking-библиотек. В production-коде Android использование на главном потоке категорически запрещено.

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

Для Android-тестов используйте kotlinx-coroutines-test с TestDispatcher вместо runBlocking. Это даёт контроль над временем, автоматический сброс и изоляцию тестов.

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

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

kotlin
    // Bad: runBlocking on Android main thread
runBlocking(Dispatchers.Main) {
    val result = networkApi.fetchData()
    textView.setText(result)
}

// Good: 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
// Test suspend function with 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)
    }
}

Для bridging между 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 блокирует поток и не реагирует на отмену lifecycle.

Чем заменить 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-тесты, bridging из callback-библиотек
  • Запрещённые сценарии — Android UI-поток, вложенные вызовы, длительные операции
  • Альтернативы — lifecycleScope, viewModelScope, runTest для тестов
  • ANR-риск — runBlocking на Main-потоке вызывает зависание приложения после 5 секунд
  • Для production-кода Android используйте асинхронные билдеры — runBlocking не предназначен для UI

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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