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("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 оправдан в трёх сценариях: точка входа main() в консольных приложениях, юнит-тесты suspend-функций и bridging — вызов suspend-кода из callback-based или blocking-библиотек. В production-коде Android использование на главном потоке категорически запрещено.
| Сценарий | Применимость | Риски |
|---|---|---|
| main() консольного приложения | Да | Нет — это точка входа, поток не блокирует UI |
| JUnit тесты | Да | Минимальные — тесты синхронны по определению |
| Android UI-поток | Нет | ANR, лаги, зависание интерфейса |
| Callback → Coroutine | Да, с осторожностью | Блокировка пула потоков при длительных операциях |
Для Android-тестов используйте kotlinx-coroutines-test с TestDispatcher вместо runBlocking. Это даёт контроль над временем, автоматический сброс и изоляцию тестов.
В большинстве сценариев runBlocking можно и нужно заменить на асинхронные альтернативы. Для Android это viewModelScope, lifecycleScope или CoroutineScope с правильным диспетчером. Для тестов — TestCoroutineDispatcher и runTest.
// 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 с виртуальным временем, позволяя тестировать задержки без реального ожидания. Это ускоряет тесты и делает их детерминированными.
Наиболее частый сценарий — тестирование suspend-функций. runBlocking в тестах позволяет синхронно дождаться результата корутины без изменения архитектуры. Второй сценарий — библиотеки с callback API, где suspend-функции вызываются из blocking-контекста через runBlocking.
// 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.
Золотое правило: runBlocking — это мост, а не замена. Используйте его только для соединения blocking- и non-blocking-миров. Для всех остальных задач применяйте launch, async или lifecycleScope.
Часто задаваемые вопросы
runBlocking — единственный билдер, который не является suspend-функцией. Он запускает event-loop в текущем потоке и не возвращает управление, пока все корутины не завершены. launch и async возвращают управление немедленно, выполняя корутину в фоне.
Не рекомендуется. ViewModel имеет встроенный viewModelScope, который автоматически управляет корутинами и отменяет их при уничтожении. runBlocking в ViewModel блокирует поток и не реагирует на отмену lifecycle.
Используйте runTest из библиотеки kotlinx-coroutines-test. Он предоставляет TestCoroutineScope с контролем виртуального времени, автоматической отменой и детерминированным выполнением.
Event-loop — цикл обработки событий внутри runBlocking. Когда корутина приостанавливается (например, delay()), event-loop переключается на выполнение других готовых корутин в том же потоке. Это создаёт иллюзию многозадачности без переключения потоков.
Вложенный runBlocking в одном потоке создаёт deadlock — внешний блок ожидает внутренний, но внутренний не может начаться, пока внешний не завершится. В разных потоках это допустимо, но крайне не рекомендуется из-за сложности отладки.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также