Passer au contenu principal

🚀 Pourquoi les coroutines ?

📖 Qu'est-ce qu'une coroutine ?

Les coroutines sont une fonctionnalité essentielle de Kotlin qui rend la programmation asynchrone facile et intuitive. Vous pouvez vous libérer de l'enfer des callbacks et écrire du code comme s'il était synchrone !

💡 Les problèmes de l'asynchrone traditionnel

L'enfer des callbacks

// ❌ Approche par callbacks (difficile à lire et à maintenir)
fun fetchUser(userId: String, callback: (User?) -> Unit) {
fetchFromNetwork(userId) { result ->
if (result != null) {
fetchProfile(result.id) { profile ->
if (profile != null) {
fetchPosts(profile.id) { posts ->
callback(User(result, profile, posts))
}
} else {
callback(null)
}
}
} else {
callback(null)
}
}
}

Le coût des threads

// ❌ Les threads sont coûteux
fun downloadFiles() {
repeat(1000) {
Thread {
// Chaque thread consomme beaucoup de mémoire
downloadFile("file_$it.txt")
}.start()
}
// Risque de manque de mémoire !
}

✨ Les avantages des coroutines

1. Code concis

// ✅ Coroutines (facile à lire et intuitif)
suspend fun fetchUser(userId: String): User? {
val result = fetchFromNetwork(userId) ?: return null
val profile = fetchProfile(result.id) ?: return null
val posts = fetchPosts(profile.id)
return User(result, profile, posts)
}

2. Concurrence légère

// ✅ Des milliers de coroutines sans problème
suspend fun downloadFiles() {
coroutineScope {
repeat(10000) {
launch {
// Coroutines légères !
downloadFile("file_$it.txt")
}
}
}
}

3. Concurrence structurée

// ✅ Gestion automatique du cycle de vie
suspend fun processData() {
coroutineScope {
val job1 = launch { task1() }
val job2 = launch { task2() }

// Se termine automatiquement quand toutes les tâches sont finies
}
}

🎯 Exemples d'utilisation réelle

Requêtes réseau

// Propre et sans callbacks !
suspend fun loadUserData(userId: String): UserData {
val user = apiService.getUser(userId)
val posts = apiService.getPosts(userId)
val friends = apiService.getFriends(userId)

return UserData(user, posts, friends)
}

Mise à jour de l'UI

// Sans bloquer le thread principal
suspend fun updateUI() {
val data = withContext(Dispatchers.IO) {
// Charger les données en arrière-plan
loadDataFromDatabase()
}

// Mettre à jour l'UI sur le thread principal
updateViews(data)
}

Traitement parallèle

// Plusieurs tâches en même temps !
suspend fun fetchAllData(): CombinedData = coroutineScope {
val users = async { fetchUsers() }
val products = async { fetchProducts() }
val orders = async { fetchOrders() }

CombinedData(
users.await(),
products.await(),
orders.await()
)
}

🔥 Coroutines vs Threads

CaractéristiqueCoroutinesThreads
PoidsTrès légerLourd
Coût de créationPresque nulÉlevé
NombreDes dizaines de milliers possiblesLimite à quelques milliers
Coût de changementFaibleÉlevé
AnnulationFacileDifficile

Comparaison des performances

// Coroutines : exécution de 100 000
suspend fun testCoroutines() {
val time = measureTimeMillis {
coroutineScope {
repeat(100_000) {
launch {
delay(1000)
}
}
}
}
println("Coroutines : ${time}ms") // ~1000ms
}

// Threads : même avec seulement 10 000...
fun testThreads() {
val time = measureTimeMillis {
repeat(10_000) {
Thread {
Thread.sleep(1000)
}.start()
}
}
println("Threads : ${time}ms") // Risque de manque de mémoire !
}

🎨 Concepts clés des coroutines

Suspend (suspension)

// Les fonctions suspend ne peuvent être appelées que dans une coroutine
suspend fun doWork() {
delay(1000) // Attente sans bloquer le thread
println("Tâche terminée")
}

Concurrence structurée

// Gestion du cycle de vie par relation parent-enfant
fun main() = runBlocking {
launch { // Parent
launch { // Enfant 1
delay(1000)
println("Enfant 1 terminé")
}
launch { // Enfant 2
delay(2000)
println("Enfant 2 terminé")
}
println("Le parent attend que les enfants se terminent")
}
}

🤔 Questions fréquemment posées

Q1. Les coroutines sont-elles de nouveaux threads ?

R : Non ! Les coroutines sont des unités de travail légères qui s'exécutent au-dessus des threads.

// Plusieurs coroutines peuvent s'exécuter sur un seul thread
fun main() = runBlocking {
repeat(3) { i ->
launch {
println("Coroutine $i : ${Thread.currentThread().name}")
}
}
}
// Toutes peuvent s'exécuter sur le même thread !

Q2. Faut-il faire de toutes les fonctions des fonctions suspend ?

R : Non ! Seules les fonctions nécessitant des opérations asynchrones doivent être suspend.

// ❌ Pas nécessaire
suspend fun add(a: Int, b: Int) = a + b

// ✅ Nécessaire
suspend fun fetchData() = withContext(Dispatchers.IO) {
loadFromNetwork()
}

Q3. Quand doit-on utiliser les coroutines ?

R : Utilisez-les dans ces cas !

// ✅ Requêtes réseau
suspend fun loadData() = api.fetchData()

// ✅ Opérations de base de données
suspend fun saveUser(user: User) = db.insert(user)

// ✅ I/O de fichiers
suspend fun readFile(path: String) = withContext(Dispatchers.IO) {
File(path).readText()
}

// ✅ Calculs longs
suspend fun heavyComputation() = withContext(Dispatchers.Default) {
// Calculs complexes
}

🎬 Conclusion

Rendez la programmation asynchrone facile avec les coroutines !

Résumé des points clés :
✅ Échappez à l'enfer des callbacks
✅ Concurrence légère
✅ Code intuitif
✅ Cycle de vie structuré
✅ Bien plus efficace que les threads

Prochaine étape : Essayez les coroutines en pratique dans Les bases des coroutines !