runBlocking — un Coroutine Builder in Kotlin che blocca il thread corrente fino al completamento della coroutine passata. A differenza di launch e async, non è una funzione suspend e può essere chiamato da codice normale (bloccante). Secondo la documentazione JetBrains, 2024, runBlocking funge da ponte tra il mondo sincrono e asincrono, consentendo di avviare coroutine dalla funzione main e dai test.
Punti chiave
runBlocking è una funzione Kotlin che crea un nuovo CoroutineScope ed esegue la coroutine passata, bloccando il thread corrente fino al suo completo completamento. A differenza di tutti gli altri Coroutine Builder, runBlocking non è una funzione suspend e può essere chiamato da codice sincrono ordinario. La firma di runBlocking accetta un CoroutineContext e un blocco suspend, restituendo un risultato di tipo T.
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
runBlocking avvia un nuovo event-loop sul thread corrente. Quando la coroutine chiama una funzione suspend (ad esempio, delay() o await()), runBlocking blocca il thread ed esegue altre coroutine programmate sullo stesso thread fino a quando quella sospesa non riprende. Questo è blocco cooperativo — il thread non è inattivo ma elabora altre coroutine.
Il meccanismo interno di runBlocking si basa su un event-loop: quando viene chiamata una funzione suspend, runBlocking mette in pausa l'esecuzione del blocco corrente ed esegue altre coroutine dalla coda. Quando la funzione suspend termina, l'esecuzione riprende. Questo ciclo continua fino a quando tutte le coroutine non sono terminate.
runBlocking utilizza il proprio pool a thread singolo per eseguire le coroutine. A differenza di Dispatchers.IO o Default, runBlocking non cambia thread — gestisce tutte le coroutine sul thread corrente, intervallando la loro esecuzione. Questo è l'unico builder che garantisce l'esecuzione sullo stesso thread.
fun main() {
val threadName = Thread.currentThread().getName()
println("Prima di runBlocking su $threadName")
val result = runBlocking {
println("Dentro runBlocking su ${Thread.currentThread().getName()}")
delay(500L)
"Done"
}
println("Dopo runBlocking: $result")
}
L'output mostrerà che tutte e tre le istruzioni println vengono eseguite sullo stesso thread. runBlocking non cambia thread ma organizza il multitasking cooperativo all'interno di un singolo thread utilizzando un event-loop.
runBlocking è giustificato in tre scenari: il punto di ingresso main() nelle applicazioni console, i test unitari delle funzioni suspend e il ponte — chiamare codice suspend da librerie basate su callback o bloccanti. Nel codice Android di produzione, usarlo sul thread principale è severamente vietato.
| Scenario | Applicabilità | Rischi |
|---|---|---|
| main() di applicazione console | Sì | Nessuno — è il punto di ingresso, il thread non blocca l'UI |
| Test JUnit | Sì | Minimi — i test sono sincroni per definizione |
| Thread UI Android | No | ANR, rallentamenti, congelamento dell'interfaccia |
| Callback → Coroutine | Sì, con cautela | Blocco del pool di thread con operazioni lunghe |
Per i test Android, utilizzare kotlinx-coroutines-test con TestDispatcher invece di runBlocking. Questo offre controllo del tempo, pulizia automatica e isolamento dei test.
Nella maggior parte degli scenari, runBlocking può e deve essere sostituito con alternative asincrone. Per Android, queste sono viewModelScope, lifecycleScope o CoroutineScope con il dispatcher appropriato. Per i test — TestCoroutineDispatcher e runTest.
// Male: runBlocking sul thread principale Android
runBlocking(Dispatchers.Main) {
val result = networkApi.fetchData()
textView.setText(result)
}
// Bene: lifecycleScope
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
textView.setText(result)
}
Per i test, la sostituzione è runTest della libreria kotlinx-coroutines-test. Crea un TestCoroutineScope con tempo virtuale, consentendo di testare i ritardi senza attesa reale. Questo accelera i test e li rende deterministici.
Lo scenario più comune è testare le funzioni suspend. runBlocking nei test consente di attendere in modo sincrono un risultato della coroutine senza modificare l'architettura. Il secondo scenario riguarda le librerie con API di callback, dove le funzioni suspend vengono chiamate da un contesto bloccante tramite runBlocking.
// Testare funzione 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)
}
}
Per il ponte tra i mondi callback e suspend, utilizzare CompletableDeferred in combinazione con runBlocking invece di callback — questo semplifica le catene di operazioni asincrone e migliora la leggibilità del codice.
L'uso improprio di runBlocking è uno degli errori comuni durante la transizione da un approccio bloccante alle coroutine. Principali problemi: chiamata sul thread principale Android, annidamento di runBlocking, uso all'interno di funzioni asincrone ed esecuzione di operazioni lunghe tramite runBlocking.
Regola d'oro: runBlocking è un ponte, non una sostituzione. Usalo solo per connettere mondi bloccanti e non bloccanti. Per tutte le altre attività, usa launch, async o lifecycleScope.
Domande frequenti
runBlocking è l'unico builder che non è una funzione suspend. Avvia un event-loop sul thread corrente e non restituisce il controllo fino a quando tutte le coroutine non sono completate. launch e async restituiscono il controllo immediatamente, eseguendo la coroutine in background.
Sconsigliato. ViewModel dispone di un viewModelScope integrato che gestisce automaticamente le coroutine e le annulla alla distruzione. runBlocking in ViewModel blocca il thread e non risponde all'annullamento del ciclo di vita.
Usare runTest dalla libreria kotlinx-coroutines-test. Fornisce un TestCoroutineScope con controllo del tempo virtuale, annullamento automatico ed esecuzione deterministica.
Event-loop è un ciclo di elaborazione degli eventi all'interno di runBlocking. Quando una coroutine viene sospesa (es. delay()), l'event-loop passa all'esecuzione di altre coroutine pronte sullo stesso thread. Questo crea l'illusione del multitasking senza cambio di thread.
runBlocking annidato sullo stesso thread crea un deadlock — il blocco esterno attende quello interno, ma quello interno non può iniziare fino a quando l'esterno non termina. Su thread diversi è consentito ma fortemente sconsigliato a causa della complessità di debug.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche