Introduction à la programmation asynchrone en Kotlin
Les applications modernes nécessitent des performances élevées et une réactivité optimale. La programmation asynchrone en Kotlin est un outil puissant qui permet d'écrire du code non bloquant en utilisant efficacement les ressources système. Contrairement au multithreading traditionnel, les coroutines Kotlin fournissent des threads d'exécution légers qui peuvent être suspendus et repris sans bloquer le thread principal.
Dans ce guide, nous allons explorer les concepts clés de la programmation asynchrone en Kotlin : des coroutines de base aux patterns avancés utilisant Flow et Channel. Vous apprendrez à créer des applications asynchrones efficaces, rapides et fiables.
Fondamentaux des coroutines en Kotlin
Qu'est-ce qu'une coroutine ?
Une coroutine est un thread d'exécution léger qui peut être suspendu à un point et repris à un autre. En Kotlin, les coroutines sont implémentées au niveau du langage à l'aide du mot-clé suspend et de builders spéciaux.
Lancement de la première coroutine
Pour travailler avec les coroutines, il est nécessaire d'ajouter la dépendance dans build.gradle.kts :
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")}Exemple de base de lancement d'une coroutine :
import kotlinx.coroutines.*
fun main() = runBlocking { launch { delay(1000L) println("Monde !") } println("Bonjour,")}Résultat de l'exécution :
Bonjour,Monde !Fonctions Suspend
Les fonctions Suspend sont des fonctions qui peuvent suspendre l'exécution d'une coroutine sans bloquer le thread. Elles sont marquées par le mot-clé suspend :
suspend fun fetchUserData(): String { delay(2000L) // simulation d'une requête réseau return "Données utilisateur"}
fun main() = runBlocking { val data = fetchUserData() println(data)}Gestion des coroutines : Scope et Dispatchers
CoroutineScope
CoroutineScope définit le cycle de vie des coroutines. Toutes les coroutines sont lancées à l'intérieur d'un scope spécifique. Si le scope est annulé, toutes ses coroutines filles sont également annulées.
class MyViewModel : ViewModel() { private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) fun loadData() { scope.launch { val data = fetchData() updateUI(data) } } override fun onCleared() { super.onCleared() scope.cancel() }}Dispatchers : choix du thread d'exécution
Les Dispatchers déterminent sur quel pool de threads la coroutine sera exécutée. Types principaux :
Dispatchers.Main— pour le thread UI (Android, JavaFX)Dispatchers.IO— pour les opérations d'entrée/sortie (réseau, fichiers)Dispatchers.Default— pour les tâches intensives en CPUDispatchers.Unconfined— non lié à un thread spécifique
fun main() = runBlocking { launch(Dispatchers.IO) { println("Exécuté sur IO : ${Thread.currentThread().name}") } launch(Dispatchers.Default) { println("Exécuté sur Default : ${Thread.currentThread().name}") }}Techniques avancées : async/await et annulation des coroutines
Exécution parallèle avec async
Le builder async permet de lancer plusieurs tâches en parallèle et d'obtenir les résultats avec await() :
suspend fun fetchDataFromApi1(): String { delay(1000L) return "Résultat API 1"}
suspend fun fetchDataFromApi2(): String { delay(1500L) return "Résultat API 2"}
fun main() = runBlocking { val time = measureTimeMillis { val result1 = async { fetchDataFromApi1() }