runBlocking — un Coroutine Builder en Kotlin qui bloque le thread actuel jusqu'à la fin de la coroutine transmise. Contrairement à launch et async, ce n'est pas une fonction suspend et il peut être appelé depuis du code normal (bloquant). Selon la documentation JetBrains, 2024, runBlocking sert de pont entre les mondes synchrone et asynchrone, permettant de lancer des coroutines depuis la fonction main et les tests.
Points clés
runBlocking est une fonction Kotlin qui crée un nouveau CoroutineScope et exécute la coroutine passée, bloquant le thread actuel jusqu'à son achèvement complet. Contrairement à tous les autres Coroutine Builders, runBlocking n'est pas une fonction suspend et peut être appelé depuis du code synchrone ordinaire. La signature de runBlocking prend un CoroutineContext et un bloc suspend, retournant un résultat de type T.
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
runBlocking lance une nouvelle event-loop sur le thread actuel. Lorsque la coroutine appelle une fonction suspend (par exemple, delay() ou await()), runBlocking bloque le thread et exécute d'autres coroutines planifiées sur le même thread jusqu'à ce que la coroutine suspendue reprenne. C'est ce qu'on appelle le blocage coopératif — le thread n'est pas inactif mais traite d'autres coroutines.
Le mécanisme interne de runBlocking est basé sur une event-loop: lors de l'appel d'une fonction suspend, runBlocking met en pause l'exécution du bloc actuel et exécute d'autres coroutines de la file d'attente. Lorsque la fonction suspend se termine, l'exécution reprend. Ce cycle continue jusqu'à ce que toutes les coroutines soient terminées.
runBlocking utilise son propre pool mono-thread pour exécuter les coroutines. Contrairement à Dispatchers.IO ou Default, runBlocking ne change pas de thread — il traite toutes les coroutines sur le thread actuel, en entrelaçant leur exécution. C'est le seul constructeur qui garantit l'exécution sur le même thread.
fun main() {
val threadName = Thread.currentThread().getName()
println("Avant runBlocking sur $threadName")
val result = runBlocking {
println("À l'intérieur de runBlocking sur ${Thread.currentThread().getName()}")
delay(500L)
"Done"
}
println("Après runBlocking: $result")
}
La sortie montrera que les trois instructions println s'exécutent sur le même thread. runBlocking ne change pas de thread mais organise du multitâche coopératif au sein d'un seul thread à l'aide d'une event-loop.
runBlocking est justifié dans trois scénarios: le point d'entrée main() dans les applications console, les tests unitaires de fonctions suspend et le pont — appeler du code suspend depuis des bibliothèques basées sur des callbacks ou bloquantes. Dans le code Android de production, son utilisation sur le thread principal est strictement interdite.
| Scénario | Applicabilité | Risques |
|---|---|---|
| main() d'application console | Oui | Aucun — c'est le point d'entrée, le thread ne bloque pas l'UI |
| Tests JUnit | Oui | Minimes — les tests sont synchrones par définition |
| Thread UI Android | Non | ANR, ralentissements, gel de l'interface |
| Callback → Coroutine | Oui, avec précaution | Blocage du pool de threads pour les opérations longues |
Pour les tests Android, utilisez kotlinx-coroutines-test avec TestDispatcher au lieu de runBlocking. Cela offre un contrôle du temps, un nettoyage automatique et un isolement des tests.
Dans la plupart des scénarios, runBlocking peut et doit être remplacé par des alternatives asynchrones. Pour Android, ce sont viewModelScope, lifecycleScope ou CoroutineScope avec le dispatcher approprié. Pour les tests — TestCoroutineDispatcher et runTest.
// Mauvais: runBlocking sur le thread principal Android
runBlocking(Dispatchers.Main) {
val result = networkApi.fetchData()
textView.setText(result)
}
// Bon: lifecycleScope
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
textView.setText(result)
}
Pour les tests, le remplacement est runTest de la bibliothèque kotlinx-coroutines-test. Il crée un TestCoroutineScope avec un temps virtuel, permettant de tester les délais sans attente réelle. Cela accélère les tests et les rend déterministes.
Le scénario le plus courant est le test des fonctions suspend. runBlocking dans les tests permet d'attendre de manière synchrone un résultat de coroutine sans changer l'architecture. Le deuxième scénario concerne les bibliothèques avec API de callbacks, où les fonctions suspend sont appelées depuis un contexte bloquant via runBlocking.
// Tester la fonction suspend avec 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)
}
}
Pour le pont entre les mondes callback et suspend, utilisez CompletableDeferred en combinaison avec runBlocking au lieu de callbacks — cela simplifie les chaînes d'opérations asynchrones et améliore la lisibilité du code.
L'utilisation inappropriée de runBlocking est l'une des erreurs fréquentes lors de la transition d'une approche bloquante vers les coroutines. Principaux problèmes: appel sur le thread principal Android, imbrication de runBlocking, utilisation dans des fonctions asynchrones et exécution d'opérations longues via runBlocking.
Règle d'or: runBlocking est un pont, pas un remplacement. Utilisez-le uniquement pour connecter les mondes bloquant et non bloquant. Pour toutes les autres tâches, utilisez launch, async ou lifecycleScope.
Questions fréquentes
runBlocking est le seul constructeur qui n'est pas une fonction suspend. Il démarre une event-loop sur le thread actuel et ne rend pas le contrôle tant que toutes les coroutines ne sont pas terminées. launch et async rendent le contrôle immédiatement, exécutant la coroutine en arrière-plan.
Déconseillé. ViewModel dispose d'un viewModelScope intégré qui gère automatiquement les coroutines et les annule lors de la destruction. runBlocking dans ViewModel bloque le thread et ne répond pas à l'annulation du cycle de vie.
Utilisez runTest de la bibliothèque kotlinx-coroutines-test. Il fournit un TestCoroutineScope avec contrôle du temps virtuel, annulation automatique et exécution déterministe.
Event-loop est un cycle de traitement d'événements dans runBlocking. Lorsqu'une coroutine est suspendue (ex: delay()), l'event-loop passe à l'exécution d'autres coroutines prêtes sur le même thread. Cela crée l'illusion de multitâche sans changement de thread.
runBlocking imbriqué sur le même thread crée un deadlock — le bloc externe attend l'interne, mais l'interne ne peut pas commencer tant que l'externe n'est pas terminé. Sur des threads différents, c'est autorisé mais fortement déconseillé en raison de la complexité de débogage.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi