runBlocking — un Coroutine Builder en Kotlin que bloquea el hilo actual hasta que finalice la corrutina pasada. A diferencia de launch y async, no es una función suspend y puede llamarse desde código normal (bloqueante). Según la documentación de JetBrains, 2024, runBlocking sirve como puente entre el mundo síncrono y asíncrono, permitiendo ejecutar corrutinas desde la función main y pruebas.
Puntos clave
runBlocking es una función de Kotlin que crea un nuevo CoroutineScope y ejecuta la corrutina pasada, bloqueando el hilo actual hasta que se complete por completo. A diferencia de todos los demás Coroutine Builders, runBlocking no es una función suspend y puede llamarse desde código síncrono normal. La firma de runBlocking toma un CoroutineContext y un bloque suspend, devolviendo un resultado de tipo T.
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
runBlocking inicia un nuevo event-loop en el hilo actual. Cuando la corrutina llama a una función suspend (por ejemplo, delay() o await()), runBlocking bloquea el hilo y ejecuta otras corrutinas programadas en el mismo hilo hasta que la suspendida se reanude. Esto es bloqueo cooperativo — el hilo no está inactivo sino que procesa otras corrutinas.
El mecanismo interno de runBlocking se basa en un event-loop: al llamar a una función suspend, runBlocking pausa la ejecución del bloque actual y ejecuta otras corrutinas de la cola. Cuando la función suspend se completa, la ejecución se reanuda. Este ciclo continúa hasta que todas las corrutinas finalizan.
runBlocking utiliza su propio grupo de un solo hilo para ejecutar corrutinas. A diferencia de Dispatchers.IO o Default, runBlocking no cambia de hilo — maneja todas las corrutinas en el hilo actual, alternando su ejecución. Este es el único builder que garantiza la ejecución en el mismo hilo.
fun main() {
val threadName = Thread.currentThread().getName()
println("Antes de runBlocking en $threadName")
val result = runBlocking {
println("Dentro de runBlocking en ${Thread.currentThread().getName()}")
delay(500L)
"Done"
}
println("Después de runBlocking: $result")
}
La salida mostrará que los tres println se ejecutan en el mismo hilo. runBlocking no cambia de hilo sino que organiza multitarea cooperativa dentro de un solo hilo usando un event-loop.
runBlocking está justificado en tres escenarios: el punto de entrada main() en aplicaciones de consola, pruebas unitarias de funciones suspend y puente — llamar a código suspend desde bibliotecas basadas en callbacks o bloqueantes. En código Android de producción, usarlo en el hilo principal está estrictamente prohibido.
| Escenario | Aplicabilidad | Riesgos |
|---|---|---|
| main() de aplicación de consola | Sí | Ninguno — es el punto de entrada, el hilo no bloquea la UI |
| Pruebas JUnit | Sí | Mínimos — las pruebas son síncronas por definición |
| Hilo UI de Android | No | ANR, rezagos, congelación de la interfaz |
| Callback → Corrutina | Sí, con precaución | Bloqueo del grupo de hilos con operaciones largas |
Para pruebas en Android, use kotlinx-coroutines-test con TestDispatcher en lugar de runBlocking. Esto proporciona control de tiempo, limpieza automática y aislamiento de pruebas.
En la mayoría de escenarios, runBlocking puede y debe reemplazarse con alternativas asíncronas. Para Android, estas son viewModelScope, lifecycleScope o CoroutineScope con el dispatcher adecuado. Para pruebas — TestCoroutineDispatcher y runTest.
// Malo: runBlocking en el hilo principal de Android
runBlocking(Dispatchers.Main) {
val result = networkApi.fetchData()
textView.setText(result)
}
// Bueno: lifecycleScope
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
textView.setText(result)
}
Para pruebas, el reemplazo es runTest de kotlinx-coroutines-test. Crea un TestCoroutineScope con tiempo virtual, permitiendo probar retrasos sin espera real. Esto acelera las pruebas y las hace deterministas.
El escenario más común es probar funciones suspend. runBlocking en pruebas permite esperar sincronizadamente un resultado de corrutina sin cambiar la arquitectura. El segundo escenario son bibliotecas con API de callbacks, donde las funciones suspend se llaman desde un contexto bloqueante mediante runBlocking.
// Probar función suspend con 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)
}
}
Para el puente entre callbacks y el mundo suspend, use CompletableDeferred en combinación con runBlocking en lugar de callbacks — esto simplifica las cadenas de operaciones asíncronas y mejora la legibilidad del código.
El uso inadecuado de runBlocking es uno de los errores comunes al migrar de un enfoque bloqueante a corrutinas. Problemas principales: llamada en el hilo principal de Android, anidamiento de runBlocking, uso dentro de funciones asíncronas y ejecución de operaciones largas con runBlocking.
Regla de oro: runBlocking es un puente, no un reemplazo. Úselo solo para conectar mundos bloqueantes y no bloqueantes. Para todas las demás tareas, use launch, async o lifecycleScope.
Preguntas frecuentes
runBlocking es el único builder que no es una función suspend. Inicia un event-loop en el hilo actual y no devuelve el control hasta que todas las corrutinas se completan. launch y async devuelven el control inmediatamente, ejecutando la corrutina en segundo plano.
No se recomienda. ViewModel tiene un viewModelScope incorporado que gestiona automáticamente las corrutinas y las cancela al destruirse. runBlocking en ViewModel bloquea el hilo y no responde a la cancelación del ciclo de vida.
Use runTest de la biblioteca kotlinx-coroutines-test. Proporciona un TestCoroutineScope con control de tiempo virtual, cancelación automática y ejecución determinista.
Event-loop es un ciclo de procesamiento de eventos dentro de runBlocking. Cuando una corrutina se suspende (ej. delay()), el event-loop cambia a ejecutar otras corrutinas listas en el mismo hilo. Esto crea la ilusión de multitarea sin cambio de hilo.
runBlocking anidado en el mismo hilo crea un deadlock — el bloque externo espera al interno, pero el interno no puede comenzar hasta que el externo termine. En diferentes hilos está permitido pero muy desaconsejado por la complejidad de depuración.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también