Les coroutines (Coroutines) sont des threads légers en Kotlin pour la programmation asynchrone, disponibles via la bibliothèque kotlinx.coroutines. Selon JetBrains Kotlin Documentation, 2026, Coroutines permettent de suspendre l'exécution d'une fonction sans bloquer un thread, contrairement aux Threads traditionnels. Les coroutines s'exécutent sur un pool de threads limité, ce qui les rend mille fois plus légères que les threads natives. Kotlin Coroutines sont entièrement intégrées à Android Jetpack, Retrofit, Room et d'autres bibliothèques populaires de l'écosystème Android.
Points Clés
Coroutines sont un mécanisme de programmation asynchrone en Kotlin, implémenté dans la bibliothèque kotlinx.coroutines. Contrairement aux threads du système d'exploitation, les coroutines ne sont pas liées à un thread spécifique : elles peuvent se suspendre sur un thread et reprendre sur un autre. Un seul thread peut exécuter des milliers de coroutines, en basculant entre elles avec une surcharge minimale.
Les coroutines sont apparues dans Kotlin 1.3 (2018) en tant que fonctionnalité expérimentale et sont devenues stables dans Kotlin 1.5 (2021). Coroutines résolvent le problème de l'enfer des callbacks de manière similaire à async/await, mais offrent une API plus riche : canaux (Channel), Flow, gestion des exceptions dans la hiérarchie Job et intégration directe avec Android Lifecycle.
Selon JetBrains (2025), chaque coroutine consomme environ 100 octets de mémoire contre 1+ Mo pour un thread natif. Cela permet d'exécuter des millions de coroutines dans une seule application sans risque d'OutOfMemoryError. C'est la légèreté des coroutines qui en fait l'outil préféré pour l'asynchronisme dans Android.
Chaque coroutine Kotlin est compilée en une machine d'état via le Continuation Passing Style (CPS). Le compilateur ajoute un paramètre Continuation caché à chaque fonction suspend. Le Continuation contient le point de reprise et toutes les variables locales. Lorsqu'une coroutine se suspend, le runtime sauvegarde le Continuation, et lorsqu'elle reprend, il le restaure sur n'importe quel thread disponible du pool du Dispatcher.
suspend est un mot-clé Kotlin qui marque une fonction comme suspensible. Une telle fonction ne peut être appelée que depuis une autre fonction suspend ou depuis une coroutine. À l'intérieur d'une fonction suspend, vous pouvez appeler d'autres fonctions suspend dans n'importe quel ordre, et chaque point d'appel est un point de suspension potentiel.
La mécanique est simple : lorsqu'une fonction suspend appelle une autre fonction suspend, elle se suspend à ce point, libérant le thread. Une fois la fonction appelée terminée, le runtime continue l'exécution à partir de l'emplacement sauvegardé. C'est ce qu'on appelle l'annulation coopérative (cooperative cancellation) — aucun thread n'est bloqué.
Important : une fonction suspend n'est pas asynchrone par défaut. L'ordre d'exécution reste séquentiel si launch ou async ne sont pas utilisés. suspend permet simplement à la fonction d'être mise en pause sans bloquer le thread et de faire partie du contexte de coroutine. Continuation Passing Style est un modèle de compilation où chaque fonction suspend reçoit un callback Continuation caché, et le compilateur génère une machine d'état pour gérer les suspensions et les reprises.
CoroutineScope est un contexte qui définit le cycle de vie des coroutines. Toutes les coroutines doivent être lancées dans un scope. Lorsqu'un scope est annulé (par exemple, à la fin d'une Activity), toutes ses coroutines filles sont automatiquement annulées. Cela évite les fuites de tâches en arrière-plan. Android Jetpack fournit des scopes prêts à l'emploi pour chaque composant : viewModelScope pour ViewModel et lifecycleScope pour Activity et Fragment, qui sont automatiquement annulés lorsque le composant correspondant est détruit.
La concurrence structurée (Structured Concurrency) est un principe qui garantit qu'une coroutine ne se termine pas avant que toutes ses coroutines filles ne soient terminées. La hiérarchie Job forme un arbre : une coroutine racine crée un job parent, les enfants créent des jobs enfants. L'annulation d'un job parent se propage à tous les enfants. Structured Concurrency est une différence fondamentale entre les coroutines et les threads.
| Scope | Où il est utilisé | Annulation |
|---|---|---|
| GlobalScope | Uniquement pour les tâches démons | N'est pas annulé automatiquement |
| viewModelScope | Android ViewModel | Lors du nettoyage du ViewModel |
| lifecycleScope | Android Activity/Fragment | Lors de la destruction du lifecycle |
| coroutineScope | Dans une fonction suspend | Lors de l'annulation du job parent |
Un Job normal annule tous les siblings lorsqu'une coroutine fille échoue. SupervisorJob est une exception : un échec dans une coroutine fille n'affecte pas les autres. Ceci est important lorsque plusieurs tâches indépendantes sont exécutées en parallèle et que l'une d'elles peut échouer sans avoir besoin d'annuler les autres.
Dispatchers déterminent sur quels threads les coroutines sont exécutées. Dispatchers.Main — le thread principal de l'UI Android. Dispatchers.IO — un pool pour les opérations bloquantes (réseau, disque). Dispatchers.Default — pour les tâches intensives en CPU. Dispatchers.Unconfined — démarre dans le thread actuel mais ne garantit pas d'y rester. Choisir le bon Dispatcher est crucial pour les performances : une tâche IO sur Default bloquera le pool de calcul, tandis qu'une tâche CPU sur IO créera des threads inutiles.
withContext — une fonction pour changer de Dispatcher à l'intérieur d'une coroutine. Par exemple, une fonction suspend qui analyse du JSON peut basculer sur Dispatchers.Default pour le calcul et revenir sur Dispatchers.Main pour mettre à jour l'UI. withContext est le constructeur le plus utilisé dans le développement Android.
launch — lance une coroutine, retourne un Job, ne retourne pas de résultat (fire-and-forget). async — lance une coroutine, retourne un Deferred dont le résultat peut être obtenu via await. runBlocking — bloque le thread actuel pour exécuter une coroutine (uniquement pour les tests et les fonctions main). Choix du constructeur dépend du scénario : launch convient pour les événements et les mises à jour, async pour les tâches avec résultat, runBlocking uniquement pour les tests ou les points d'entrée.
Considérons trois scénarios pratiques : une coroutine de base avec launch, un appel parallèle avec async et la gestion des erreurs avec SupervisorJob.
viewModelScope.launch lance une coroutine dans le contexte du ViewModel. Lorsque le ViewModel est nettoyé, la coroutine est automatiquement annulée.
class ProfileViewModel : ViewModel() {
fun loadUser() {
viewModelScope.launch(Dispatchers.IO) {
val user = api.fetchUser()
withContext(Dispatchers.Main) {
showUser(user)
}
}
}
}
coroutineScope avec async lance trois requêtes en parallèle. Les résultats sont collectés via .await(). Si une requête échoue, toutes sont annulées.
suspend fun loadDashboard(): Dashboard = coroutineScope {
val user = async { api.fetchUser() }
val posts = async { api.fetchPosts() }
val stats = async { api.fetchStats() }
Dashboard(user.await(), posts.await(), stats.await())
}
SupervisorJob permet à chaque coroutine de se terminer indépendamment. Une erreur dans une requête n'annule pas les autres.
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
scope.launch {
try { api.fetchUsers() } catch (e: Exception) { log(e) }
}
scope.launch {
try { api.fetchPosts() } catch (e: Exception) { log(e) }
}
Les threads sont une primitive du système d'exploitation. Chaque thread a sa propre pile (~1 Mo) et nécessite un appel système pour la création et le changement. Les coroutines sont une primitive du langage, non liée à l'OS. Elles utilisent Continuation pour sauvegarder l'état et basculent au niveau du runtime sans appels système.
Selon Google (2025), l'utilisation de coroutines au lieu de threads réduit la consommation mémoire pour les tâches d'arrière-plan dans les applications Android de 90 à 95 %. Toutes les bibliothèques Android modernes (Retrofit, Room, WorkManager) ont un support intégré des coroutines via les fonctions suspend. Ktor (framework client HTTP de JetBrains) est également entièrement construit sur les coroutines, fournissant des fonctions suspend pour chaque requête sans API de callbacks. Room prend en charge les coroutines via des fonctions suspend dans le DAO, permettant d'exécuter des requêtes de base de données sans bloquer le thread principal.
Les threads restent nécessaires pour le code natif via JNI, les appels bloquants CPU-intensive sans limite de temps (rendu vidéo, simulations) et lors de l'intégration avec des bibliothèques C. Pour tout le reste — des coroutines.
Questions Fréquentes
Coroutine est une unité de travail suspensible qui s'exécute sur un thread existant. Un thread est une ressource système avec sa propre pile. Les coroutines sont des milliers de fois plus légères que les threads et ne bloquent pas les ressources lors de la suspension.
Dispatchers.IO est conçu pour les opérations d'E/S bloquantes (réseau, fichiers) et peut créer de nouveaux threads si nécessaire. Dispatchers.Default a un pool de taille fixe (nombre de cœurs CPU) pour les calculs intensifs en CPU.
Job.cancel() annule la coroutine et tous ses enfants. Pour vérifier l'annulation à l'intérieur d'une coroutine, utilisez ensureActive() — il lève une CancellationException si la coroutine est annulée.
Oui — via la bibliothèque kotlinx-coroutines-rx3. Elle fournit les fonctions awaitSingle, awaitFirst et autres pour convertir Observable/Single en fonctions suspend et inversement via flowable.
Flow est un flux de données asynchrone à froid, l'équivalent dans les coroutines de RxJava Observable. Flow émet des valeurs séquentiellement et se termine par une exception ou un succès. Il prend en charge map, filter, catch et d'autres opérateurs.
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