Sobes.tech
Middle

Comment configurer et utiliser les coroutines dans des projets Kotlin?

sobes.tech IA

Réponse de l'IA

Pour configurer les coroutines, il est nécessaire d'ajouter la dépendance correspondante dans le fichier build.gradle.

// build.gradle (module app)
dependencies {
    //... autres dépendances
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") // Pour JVM, Android, JS, Native
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") // Pour Android (si Dispatchers.Main est nécessaire)
}

L'utilisation des coroutines inclut :

  1. Portée (Scope) : Définir la portée dans laquelle les coroutines s'exécuteront et gérer leur cycle de vie.

    • GlobalScope : Portée de toute l'application, à utiliser avec précaution, car cela peut entraîner des fuites de mémoire.
    • CoroutineScope : Créé explicitement, permet de contrôler l'annulation des coroutines.
    • Portées prédéfinies dans les frameworks (par exemple, lifecycleScope dans Android ViewModel).
    // Exemple avec CoroutineScope
    val myScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) // Création d'une portée avec Dispatcher et Job
    
    fun performBackgroundTask() {
        myScope.launch { // Lancer une coroutine
            // Processus de calcul long
        }
    }
    
    fun cancelAllTasks() {
        myScope.cancel() // Annuler toutes les coroutines dans la portée
    }
    
  2. Constructeurs de coroutines (Builders) : Fonctions pour lancer des coroutines.

    • launch : Lance une coroutine qui ne retourne pas de résultat explicite (retourne un Job).
    • async : Lance une coroutine qui retourne un résultat sous forme de Deferred.
    import kotlinx.coroutines.*
    
    suspend fun fetchData() = coroutineScope { // coroutineScope - constructeur pour la concurrence structurée
        val data1 = async { networkCall1() } // async retourne un Deferred
        val data2 = async { networkCall2() }
    
        // ... traitement des données après leur réception
        data1.await() // Obtenir le résultat de Deferred, suspend la coroutine jusqu'à ce qu'il soit terminé
        data2.await()
    }
    
    suspend fun networkCall1(): String {
        delay(1000) // Simulation d'une requête réseau
        return "Data 1"
    }
    
    suspend fun networkCall2(): String {
        delay(1500)
        return "Data 2"
    }
    
    fun startFetching() = runBlocking { // runBlocking - constructeur pour bloquer le thread actuel (pour main ou tests)
        launch { // launch retourne un Job
            println("Fetching data...")
            fetchData()
            println("Data fetched!")
        }
    }
    
  3. Distributeurs (Dispatchers) : Définissent sur quel thread ou pool de threads la coroutine sera exécutée.

    • Dispatchers.Default : Pour les tâches intensives en CPU.
    • Dispatchers.IO : Pour les opérations d'E/S bloquantes (fichiers, réseau).
    • Dispatchers.Main : Thread principal (seulement pour Android et Swing/JavaFX).
    • Dispatchers.Unconfined : Lance la coroutine dans le thread qui l'a appelée, elle se suspend et reprend dans le thread qui l'a relancée.
    import kotlinx.coroutines.*
    
    fun simpleTask() {
        GlobalScope.launch(Dispatchers.IO) { // Spécification du distributeur
            // Exécution d'une opération d'E/S
            println("Exécuté sur le thread : ${Thread.currentThread().name}")
        }
    }
    
    fun main() = runBlocking {
        launch(Dispatchers.Default) {
            println("Thread par défaut : ${Thread.currentThread().name}")
        }
        launch(Dispatchers.Unconfined) {
             println("Thread non confiné 1 : ${Thread.currentThread().name}")
             delay(100) // Suspension
             println("Thread non confiné 2 : ${Thread.currentThread().name}") // Reprise dans un autre thread (possiblement)
        }
        delay(200) // Attendre la fin des coroutines
    }
    
  4. Fonctions de suspension (Suspending functions) : Fonctions marquées avec le mot-clé suspend, qui peuvent être suspendues et reprises sans bloquer le thread. Ne peuvent être appelées que depuis d'autres fonctions de suspension ou blocs de coroutines.

  5. Gestion de l'annulation et des exceptions : Les coroutines supportent l'annulation structurée et la propagation des exceptions.

    • Annulation :
      import kotlinx.coroutines.*
      
      fun cancellableTask() = GlobalScope.launch {
          try {
              repeat(1000) { i ->
                  println("Travail $i ...")
                  delay(100) // fonction de suspension respectant l'annulation
              }
          } catch (e: CancellationException) {
              println("Tâche annulée!")
          } finally {
              println("Nettoyage...")
          }
      }
      
      fun main() = runBlocking {
          val job = cancellableTask()
          delay(500) // Attendre un peu
          job.cancelAndJoin() // Annuler et attendre la fin
      }
      
    • Exceptions : En cas de launch, une exception non capturée entraînera la chute de l'application (sur Android, par défaut). En cas de async, l'exception sera levée lors de l'appel à await(). Pour un contrôle plus granulaire, on utilise CoroutineExceptionHandler ou SupervisorJob.
    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        val handler = CoroutineExceptionHandler { _, exception ->
            println("Exception capturée : $exception")
        }
    
        val job = GlobalScope.launch(handler) { // Application du gestionnaire
            throw IllegalStateException("Quelque chose a mal tourné")
        }
        job.join() // Attendre la fin
    }