Passer au contenu principal

🎯 Bases des coroutines

📖 Débuter avec les coroutines

Pour utiliser les coroutines, vous devez d'abord connaître les constructeurs de coroutines. Découvrons les trois constructeurs les plus fondamentaux !

💡 runBlocking

Utilisation de base

import kotlinx.coroutines.*

fun main() = runBlocking { // Démarrage de la coroutine !
println("Hello")
delay(1000) // Attendre 1 seconde
println("World!")
}

Caractéristiques

fun main() {
println("Before")

runBlocking {
delay(1000)
println("Inside coroutine")
}

println("After") // Attend que la coroutine se termine
}
// Before
// (attente de 1 seconde)
// Inside coroutine
// After

Attention : runBlocking bloque le thread actuel ! Utilisez-le principalement uniquement dans les tests ou les fonctions main.

🚀 launch

Démarrer une tâche asynchrone

fun main() = runBlocking {
launch {
delay(1000)
println("Task 1")
}

launch {
delay(500)
println("Task 2")
}

println("Main")
}
// Main
// (après 500ms) Task 2
// (après 1000ms) Task 1

Retour de Job

fun main() = runBlocking {
val job = launch {
repeat(5) { i ->
println("Working... $i")
delay(500)
}
}

delay(1300)
println("Annulation de la tâche !")
job.cancel() // Annuler la tâche
job.join() // Attendre la fin de l'annulation
}

Gérer plusieurs tâches

fun main() = runBlocking {
val jobs = List(5) { i ->
launch {
delay(1000L * i)
println("Tâche $i terminée")
}
}

jobs.forEach { it.join() } // Attendre toutes les tâches
println("Toutes les tâches terminées !")
}

⚡ async

Recevoir un résultat

fun main() = runBlocking {
val deferred = async {
delay(1000)
return@async "Valeur de résultat"
}

println("En cours de traitement...")
val result = deferred.await() // Attendre le résultat
println("Valeur reçue : $result")
}

launch vs async

fun main() = runBlocking {
// launch - pas de résultat
launch {
delay(1000)
println("launch terminé")
}

// async - retourne un résultat
val result = async {
delay(1000)
"async terminé"
}

println(result.await())
}

🎯 Exemples pratiques

Traitement parallèle

import kotlin.system.measureTimeMillis

fun main() = runBlocking {
val time = measureTimeMillis {
val one = async { fetchData1() }
val two = async { fetchData2() }

println("Résultat : ${one.await()}, ${two.await()}")
}

println("Temps écoulé : ${time}ms") // ~1000ms (exécution parallèle)
}

suspend fun fetchData1(): String {
delay(1000)
return "Données1"
}

suspend fun fetchData2(): String {
delay(1000)
return "Données2"
}

Séquentiel vs Parallèle

fun main() = runBlocking {
// ❌ Exécution séquentielle (lente)
val time1 = measureTimeMillis {
val one = fetchData1()
val two = fetchData2()
println("$one, $two")
}
println("Séquentiel : ${time1}ms") // ~2000ms

// ✅ Exécution parallèle (rapide)
val time2 = measureTimeMillis {
val one = async { fetchData1() }
val two = async { fetchData2() }
println("${one.await()}, ${two.await()}")
}
println("Parallèle : ${time2}ms") // ~1000ms
}

Gestion des exceptions

fun main() = runBlocking {
val deferred = async {
delay(1000)
throw Exception("Erreur survenue !")
}

try {
deferred.await()
} catch (e: Exception) {
println("Erreur capturée : ${e.message}")
}
}

🔥 Modèles pratiques

Timeout

fun main() = runBlocking {
try {
withTimeout(1300) {
repeat(3) { i ->
println("Tâche $i")
delay(500)
}
}
} catch (e: TimeoutCancellationException) {
println("Délai expiré !")
}
}

Appels API multiples

data class UserData(
val profile: String,
val posts: List<String>,
val friends: List<String>
)

suspend fun loadUserData(userId: String): UserData = coroutineScope {
val profile = async { fetchProfile(userId) }
val posts = async { fetchPosts(userId) }
val friends = async { fetchFriends(userId) }

UserData(
profile.await(),
posts.await(),
friends.await()
)
}

suspend fun fetchProfile(id: String): String {
delay(500)
return "Profile($id)"
}

suspend fun fetchPosts(id: String): List<String> {
delay(800)
return listOf("Post1", "Post2")
}

suspend fun fetchFriends(id: String): List<String> {
delay(600)
return listOf("Friend1", "Friend2")
}

Logique de nouvelle tentative

suspend fun <T> retryIO(
times: Int = 3,
delay: Long = 1000,
block: suspend () -> T
): T {
repeat(times - 1) { attempt ->
try {
return block()
} catch (e: Exception) {
println("Tentative ${attempt + 1} échouée")
delay(delay)
}
}
return block() // Dernière tentative
}

fun main() = runBlocking {
val result = retryIO(times = 3) {
fetchDataFromAPI()
}
println(result)
}

var attempt = 0
suspend fun fetchDataFromAPI(): String {
attempt++
if (attempt < 3) {
throw Exception("Erreur réseau")
}
return "Succès !"
}

🛠️ coroutineScope

Concurrence structurée

suspend fun doWork() = coroutineScope {
launch {
delay(1000)
println("Tâche 1")
}

launch {
delay(2000)
println("Tâche 2")
}

println("Toutes les tâches démarrées")
// Attend que tous les enfants se terminent
}

fun main() = runBlocking {
doWork()
println("Terminé !")
}

Propagation des exceptions

suspend fun riskyWork() = coroutineScope {
launch {
delay(500)
throw Exception("Erreur !")
}

launch {
delay(1000)
println("Ce code ne s'exécutera pas")
}
}

fun main() = runBlocking {
try {
riskyWork()
} catch (e: Exception) {
println("Erreur capturée : ${e.message}")
}
}

🤔 Questions fréquemment posées

Q1. Quelle est la différence entre launch et async ?

R : La présence ou non d'un retour de résultat !

fun main() = runBlocking {
// launch - pas besoin de résultat
launch {
println("Tâche simple")
}

// async - besoin de résultat
val result = async {
"Valeur à retourner"
}
println(result.await())
}

Q2. Quelle est la différence entre join() et await() ?

R : join attend la fin, await attend le résultat !

fun main() = runBlocking {
val job = launch {
delay(1000)
}
job.join() // Attend seulement la fin

val deferred = async {
delay(1000)
"Résultat"
}
val result = deferred.await() // Attend le résultat
}

Q3. Les coroutines sont-elles automatiquement annulées ?

R : Si le parent est annulé, les enfants le sont aussi !

fun main() = runBlocking {
val parent = launch {
val child = launch {
repeat(10) {
println("Tâche enfant $it")
delay(500)
}
}

delay(1300)
println("Parent terminé")
}

delay(2000)
parent.cancel() // L'enfant est également annulé
}

🎬 Conclusion

Vous avez maîtrisé les constructeurs de base des coroutines !

Résumé des points clés :
✅ runBlocking - pour les tests/fonctions main
✅ launch - tâches sans besoin de résultat
✅ async - recevoir un résultat
✅ coroutineScope - concurrence structurée
✅ Amélioration des performances grâce au traitement parallèle

Prochaine étape : Approfondissez les fonctions suspendues dans fonctions suspend !