Coroutines à Kotlin | AU NOUVEAU BLOG

Dans les applications modernes, en particulier celles impliquant des requêtes réseau, des E/S de fichiers ou des calculs complexes, la gestion des opérations asynchrones est essentielle pour maintenir la réactivité et les performances. Traditionnellement, les développeurs utilisaient des rappels, des threads, des contrats à terme et AsyncTask
sous Android pour gérer les tâches asynchrones. Cependant, ces méthodes entraînent souvent plusieurs défis.
Ce diagramme explique brièvement la différence entre l’exécution de tâches « synchrone » et « asynchrone ».
Défis des appels asynchrones
- Rappels : Les rappels sont des fonctions passées en arguments à d’autres fonctions, à exécuter après la fin d’une tâche. Cela peut conduire à un « enfer des rappels » ou à une « pyramide du malheur », où les rappels imbriqués deviennent difficiles à lire et à maintenir.
- Sujets : Les threads permettent une exécution parallèle mais entraînent des frais généraux liés à la gestion de la création de threads, de la synchronisation et de problèmes potentiels tels que l’épuisement des threads.
- Futurs et promesses : Les contrats à terme offrent un moyen de gérer les résultats asynchrones, mais peuvent s’avérer fastidieux, en particulier lors de l’enchaînement de plusieurs opérations asynchrones.
- AsyncTask (spécifique à Android) :
AsyncTask
était un moyen courant d’effectuer des opérations en arrière-plan sous Android sans bloquer le thread principal. Cependant, il présente plusieurs inconvénients :
Fuites de mémoire : Si un`AsyncTask`
n’est pas correctement géré, cela peut provoquer des fuites de mémoire en conservant une référence au fichier englobant`Activity
` ou`Fragment`
.
Modifications de configuration :`AsyncTask`
ne gère pas les modifications de configuration (comme les rotations d’écran) avec élégance, ce qui entraîne des plantages potentiels ou une perte de résultats.
La gestion du cycle de vie: Gérer le cycle de vie de`AsyncTask`
est complexe et sujet aux erreurs, en particulier avec des tâches imbriquées ou multiples.
Pourquoi nous avons besoin de coroutines et ce qu’elles résolvent
Les coroutines Kotlin offrent un moyen plus gérable de gérer les opérations asynchrones en vous permettant d’écrire du code non bloquant de manière séquentielle. Ils fournissent:
- Concurrence simplifiée : Les coroutines éliminent le besoin de rappels, réduisent la complexité et facilitent la lecture et la maintenance du code.
- Concurrence structurée : Les coroutines fournissent une approche structurée de la concurrence, où le cycle de vie des coroutines est lié à leur portée, garantissant une annulation et une gestion des ressources appropriées.
- Fils légers : Les coroutines sont légères, vous permettant d’en exécuter des milliers simultanément sans la surcharge des threads traditionnels.
- Solutions spécifiques à Android :
- Sensibilisation au cycle de vie : Avec des portées de coroutine liées aux composants du cycle de vie comme
viewModelScope
vous pouvez facilement gérer les cycles de vie des coroutines, évitant ainsi les fuites de mémoire et garantissant une annulation correcte. - Opérations de sécurité principale : En utilisant
Dispatchers.Main
vous pouvez mettre à jour l’interface utilisateur en toute sécurité à partir de coroutines sans bloquer le thread principal. - Gestion des modifications de configuration : Les coroutines peuvent gérer de manière transparente les modifications de configuration en utilisant des étendues qui respectent le cycle de vie Android.
- Sensibilisation au cycle de vie : Avec des portées de coroutine liées aux composants du cycle de vie comme
Création de coroutines
Kotlin propose plusieurs façons de créer et de gérer des coroutines. Les constructeurs de coroutines les plus courants sont launch
, async
et withContext
.
lancement
Le ‘launch
La fonction ` crée une nouvelle coroutine qui exécute un bloc de code de manière asynchrone. Il renvoie un Job
représentant le cycle de vie de la coroutine.
import kotlinx.coroutines.* fun main() = runBlocking { val job = launch { println("Coroutine started") delay(1000) // Simulate a task with delay println("Coroutine completed") } println("Main thread continues to execute") job.join() // Wait for the coroutine to complete println("Main thread waits for the coroutine to finish") }
`delay`
: Cette fonction met la coroutine en pause pendant une durée spécifiée sans bloquer le thread sous-jacent.`join`
: Cette fonction attend la fin de la coroutine.
async
Le ‘async`
La fonction crée une nouvelle coroutine qui exécute un bloc de code de manière asynchrone et renvoie un `Deferred`
représentant le résultat du calcul de la coroutine.
import kotlinx.coroutines.* fun main() = runBlocking { val deferred = async { println("Async computation started") delay(1000) // Simulate a long-running computation 42 // Computation result } println("Main thread continues to execute") val result = deferred.await() // Await the result of the computation println("Async computation result: $result") }
`await`
: Cette fonction attend le résultat duDeferred
coroutine.- Si vous n’utilisez pas `
await`
la coroutine continuera à s’exécuter, mais le résultat ne sera pas récupéré :
withContext
Le ‘withContext`
La fonction vous permet de changer temporairement le contexte d’une coroutine tout en préservant l’état de la coroutine.
import kotlinx.coroutines.* fun main() = runBlocking { launch(Dispatchers.Main) { println("Running on Main Dispatcher") val data = withContext(Dispatchers.IO) { println("Fetching data on IO Dispatcher") delay(1000) // Simulate network request "Data from network" } println("Received data: $data") } }
Portée de la coroutine
Une portée de coroutine définit le cycle de vie et les limites des coroutines lancées en son sein. Il garantit que toutes les coroutines démarrées dans la portée sont annulées lorsque la portée elle-même est annulée.
Utiliser `coroutineScope`
Fonction:
La fonction coroutineScope crée une nouvelle portée de coroutine et suspend l’exécution jusqu’à ce que toutes les coroutines de la portée soient terminées. Il est généralement utilisé pour gérer un groupe de coroutines associées.
import kotlinx.coroutines.* fun main() = runBlocking { coroutineScope { launch { delay(1000) println("Task 1 from coroutineScope") } launch { delay(1500) println("Task 2 from coroutineScope") } } println("coroutineScope is over") }
En utilisant `GlobalScope`
:
GlobalScope
démarre les coroutines au niveau global, indépendamment de tout cycle de vie. Il convient aux tâches d’arrière-plan de longue durée qui doivent persister tout au long de la durée de vie de l’application.
import kotlinx.coroutines.* fun main() { GlobalScope.launch { delay(1000) println("Task from GlobalScope") } Thread.sleep(1500) // Ensure the coroutine has time to complete }
Utiliser `SupervisorScope`
:
Le supervisorScope
la fonction est similaire à coroutineScope
, mais il traite différemment les échecs des coroutines enfants. Dans un supervisorScope
l’échec d’un enfant n’annule pas les autres enfants.
import kotlinx.coroutines.* fun main() = runBlocking { supervisorScope { launch { delay(500) println("Task from supervisor scope") } launch { delay(1000) throw RuntimeException("Failure in child coroutine") } } println("Supervisor scope completed") }
Utiliser `viewModelScope`
:
`viewModelScope`
fait partie des composants de l’architecture Android. Il est conçu pour gérer le cycle de vie des coroutines dans un ViewModel, en les annulant automatiquement lorsque le ViewModel est effacé.
import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.* class MyViewModel : ViewModel() { fun fetchData() { viewModelScope.launch { // Perform a network request delay(1000) println("Data fetched") } } }
Job et ses cas d’utilisation
Un `Job`
représente une unité de travail annulable au sein d’une coroutine. Il vous permet de gérer le cycle de vie d’une coroutine, y compris sa création, son achèvement et son annulation.
import kotlinx.coroutines.* fun main() = runBlocking { // Launch a coroutine and get its Job val job = launch { println("Coroutine started") delay(1000) // Simulate a task println("Coroutine completed") } println("Main thread continues to execute") // Join the job to wait for coroutine completion job.join() println("Main thread waits for the coroutine to finish") }
Cas d’utilisation expliqués
- Création d’emplois et cycle de vie : Lancez une coroutine et stockez son Job pour gérer son cycle de vie.
- Vérification de l’état du travail : Utiliser
job.isActive
pour vérifier si le travail est en cours d’exécution, terminé ou annulé. - Annulation d’emploi : Annulez le travail après 500 ms en utilisant
job.cancelAndJoin() or job.cancel() and job.join()
qui annule et attend la fin. - Gestion des exceptions d’annulation : Gérer l’annulation dans un essayer-attraper bloquer pour le nettoyage ou des actions supplémentaires.
- Bloc final : Assurez-vous que le code s’exécute dans le ‘enfin’ bloquer pour le nettoyage nécessaire, indépendamment de l’achèvement ou de l’annulation.
Fonctions de suspension
Les fonctions de suspension peuvent être suspendues et reprises. Ils permettent aux coroutines d’effectuer des opérations asynchrones sans bloquer le thread. Les fonctions de suspension ne peuvent être utilisées que dans des coroutines ou d’autres fonctions de suspension. Par exemple, vous pouvez utiliser une fonction de suspension pour effectuer une requête réseau ou lire les données d’un fichier.
import kotlinx.coroutines.* suspend fun fetchData(): String { delay(1000) // Simulate network request return "Data from network" } fun main() = runBlocking { val data = fetchData() println(data) }
Répartiteurs
Les répartiteurs contrôlent le thread sur lequel une coroutine s’exécute. Les répartiteurs courants comprennent :
`Dispatchers.Default`
: Pour les tâches gourmandes en CPU.`Dispatchers.IO`
: Pour les tâches gourmandes en E/S.`Dispatchers.Main`
: Pour interagir avec l’interface utilisateur.`Dispatchers.Unconfined
` : démarre la coroutine dans le thread appelant mais seulement jusqu’au premier point de suspension. Après cela, il reprend dans n’importe quel thread utilisé par la fonction « suspension ».
import kotlinx.coroutines.* fun main() = runBlocking { launch(Dispatchers.Default) { // Perform CPU-intensive task } launch(Dispatchers.IO) { // Perform I/O-intensive task } launch(Dispatchers.Main) { // Update UI } launch(Dispatchers.Unconfined) { // Resume in the caller thread until the first suspension point } }
En tirant parti des répartiteurs, vous pouvez garantir que les coroutines s’exécutent dans le contexte approprié, optimisant ainsi les performances et la réactivité de votre application.
Source link