Concurrence et coroutines à Kotlin

La machine virtuelle Java, ou JVM, prend en charge le multithreading. Tout processus que vous exécutez sur celui-ci est libre de créer un nombre raisonnable de threads pour effectuer plusieurs tâches de manière asynchrone. Cependant, écrire du code qui peut le faire de manière optimale et sans erreur peut être extrêmement difficile. Au fil des ans, Java, d’autres langages JVM et de nombreuses bibliothèques tierces ont essayé de proposer des approches créatives et élégantes pour résoudre ce problème..

Par exemple, Java 5 a introduit le framework d’exécuteur, qui vous permet de découpler les détails de gestion des threads de votre logique d’entreprise. Java 8 propose des flux parallèles, qui peuvent facilement être utilisés avec des expressions lambda. RxJava apporte des extensions réactives à Java, vous permettant d'écrire du code asynchrone très concis et lisible.

Kotlin soutient presque toutes ces approches et en propose quelques-unes. Dans ce tutoriel, je vais vous montrer comment les utiliser dans des applications Android..

Conditions préalables

Pour pouvoir suivre ce tutoriel, vous aurez besoin de:

  • Android Studio 2.3.3 ou supérieur
  • Kotlin plugin 1.1.51 ou supérieur
  • une compréhension de base des threads Java

Si vous n'êtes pas à l'aise avec les expressions lambda et les interfaces SAM, je vous suggère également de lire le tutoriel suivant avant de poursuivre:

Vous pouvez également apprendre tous les tenants et les aboutissants de la langue Kotlin dans notre série Kotlin From Scratch.

  • Kotlin From Scratch: nullabilité, boucles et conditions

    Kotlin est gratuit et open source, et rend le codage pour Android encore plus amusant. Dans ce tutoriel, nous examinerons la nullabilité, les boucles et les conditions dans Kotlin..
    Chike Mgbemena
    Kotlin
  • Kotlin From Scratch: Plus de plaisir avec des fonctions

    En savoir plus sur les fonctions de niveau supérieur, les expressions lambda, les fonctions anonymes, les fonctions locales, les fonctions infixes et les fonctions membres dans Kotlin.
    Chike Mgbemena
    Kotlin

1. Créer des threads

En général, les instances de classes qui implémentent le Runnable interface sont utilisés pour créer des threads dans Kotlin. Parce que le Runnable l'interface n'a qu'une méthode, la courir() méthode, vous pouvez utiliser la fonctionnalité de conversion SAM de Kotlin pour créer de nouveaux threads avec un code passe-partout minimal.

Voici comment vous pouvez utiliser le fil() Fonction, qui fait partie de la bibliothèque standard de Kotlin, pour créer et démarrer rapidement un nouveau thread:

thread // une opération longue

L'approche ci-dessus n'est appropriée que lorsque vous devez occasionnellement générer un fil ou deux. Si la simultanéité est une partie importante de la logique métier de votre application et que vous avez besoin d'un grand nombre de threads, l'utilisation de pools de threads avec un service d'exécuteur est une meilleure idée.. 

Par exemple, le code suivant utilise le code suivant: newFixedThreadPool () méthode du Des exécuteurs classe pour créer un pool de threads contenant huit threads réutilisables et exécute un grand nombre d'opérations en arrière-plan dessus:

val myService: ExecutorService = Executors.newFixedThreadPool (8) var i = 0 while (i < items.size)  // items may be a large array val item = items[i] myService.submit  processItem(item) // a long running operation  i += 1 

Cela n’est peut-être pas évident à première vue, mais dans le code ci-dessus, l’argument du soumettre() méthode du service exécuteur est en fait un Runnable objet.

2. Obtenir des résultats à partir de threads

Tâches d'arrière-plan créées à l'aide du Runnable l'interface ne peut renvoyer aucun résultat directement. Si vous souhaitez recevoir les résultats de vos discussions, vous devez utiliser le Callable interface à la place, qui est aussi une interface SAM.

Quand vous passez un Callable objecter à la soumettre() méthode d’un service exécuteur, vous recevez un Futur objet. Comme son nom l'indique, le Futur objet contiendra le résultat de la Callable à un moment donné dans le futur, lorsque le service d’exécuteur aura fini de l’exécuter. Pour obtenir le résultat réel d'un Futur objet, tout ce que vous devez faire est d'appeler son obtenir() méthode-mais méfiez-vous, votre thread va bloquer si vous l'appelez prématurément.

L’exemple de code suivant vous montre comment créer un Callable objet qui retourne un Futur de type Chaîne, lancez-le et affichez le résultat:

val myService: ExecutorService = Executors.newFixedThreadPool (2) val result = myService.submit (appelable // une opération en arrière-plan qui génère // une chaîne) // Autres opérations // Imprimer le résultat Log.d (TAG, result.get ())

3. Synchroniser les threads

Contrairement à Java, Kotlin n’a pas le synchronisé mot-clé. Par conséquent, pour synchroniser plusieurs opérations en arrière-plan, vous devez utiliser soit le @Synchronisé annotation ou la synchronisé () fonction inline de bibliothèque standard. L'annotation peut synchroniser une méthode entière et la fonction fonctionne sur un bloc d'instructions..

// une fonction synchronisée @Synchronized fun myFunction ()  fun myOtherFunction () // un bloc synchronisé synchronisé (this) 

Les deux @Synchronisé annotation et la synchronisé () fonction utiliser le concept de verrouillage du moniteur. 

Si vous ne le savez pas déjà, chaque moniteur de la machine virtuelle Java est associé à un moniteur. Pour l'instant, vous pouvez considérer un moniteur comme un jeton spécial qu'un thread peut acquérir ou verrouiller pour obtenir un accès exclusif à l'objet. Une fois que le moniteur d'un objet est verrouillé, les autres threads souhaitant travailler sur l'objet devront attendre que le moniteur soit libéré, ou déverrouillé, à nouveau..

Tandis que le @Synchronisé une annotation verrouille le moniteur de l'objet auquel appartient la méthode associée, la synchronisé () fonction peut verrouiller le moniteur de tout objet qui lui est passé comme argument.

4. Comprendre les coroutines

À travers une bibliothèque expérimentale, Kotlin propose une approche alternative pour atteindre la concurrence: les routines. Les coroutines sont beaucoup plus légères que les threads et sont beaucoup plus faciles à gérer.

Dans les applications multithread mobiles, les threads sont généralement utilisés pour des opérations telles que l'extraction d'informations sur Internet ou l'interrogation de bases de données. De telles opérations n'impliquent pas beaucoup de calculs et les threads passent la majeure partie de leur vie dans un état bloqué, attendant simplement que les données proviennent d'autre part. Comme vous pouvez probablement le constater, ce n’est pas un moyen très efficace d’utiliser le processeur..

Les Coroutines sont conçues pour être utilisées à la place des threads pour de telles opérations. La chose la plus importante à comprendre à propos des coroutines est qu’elles peuvent être suspendues. En d'autres termes, au lieu de bloquer, ils peuvent simplement s'arrêter lorsque cela est nécessaire et continuer en toute transparence ultérieurement. Cela conduit à une meilleure utilisation du processeur. En effet, avec des coroutines bien conçues, vous pouvez exécuter sans effort des dizaines d'opérations d'arrière-plan.

Pour pouvoir utiliser des routines dans votre projet Android Studio, assurez-vous d’ajouter les éléments suivants: compiler dépendance dans le app modules build.gradle fichier:

compiler 'org.jetbrains.kotlinx: kotlinx-coroutines-android: 0.19.3'

5. Créer des fonctions de suspension

Une coroutine ne peut être suspendue qu'avec l'aide d'une fonction de suspension. Par conséquent, la plupart des coroutines ont des appels vers au moins une de ces fonctions..

Pour créer une fonction de suspension, il suffit d’ajouter le suspendre modificateur à une fonction régulière. Voici une fonction de suspension typique exécutant une requête HTTP GET à l'aide de la bibliothèque khttp:

suspend fun fun fetchWebsiteContents (url: String): String return khttp.get (url) .text

Notez qu'une fonction de suspension ne peut être appelée que par une coroutine ou une autre fonction de suspension. Si vous essayez de l'appeler de n'importe où ailleurs, votre code ne sera pas compilé.

6. Créer des coroutines

Lorsqu'il s'agit de créer une nouvelle coroutine, la bibliothèque standard de Kotlin a suffisamment de constructeurs de coroutine pour que vous ayez l'embarras du choix. Le constructeur de coroutine le plus simple que vous pouvez utiliser est le lancement() fonction, et comme la plupart des autres constructeurs de coroutines, il s’attend à une suspension de lambda, qui n’est autre qu’une fonction de suspension anonyme. En tant que tel, ce lambda est ce qui devient la coroutine.

Le code suivant crée une coroutine qui effectue deux appels séquentiels à la fonction de suspension créée à l'étape précédente:

val job1 = lancer val website1 = fetchWebsiteContents ("https://code.tutsplus.com") val website2 = fetchWebsiteContents ("https://design.tutsplus.com")

La valeur de retour de la lancement() la fonction est un Emploi objet, que vous pouvez utiliser pour gérer la coroutine. Par exemple, vous pouvez appeler son joindre() méthode pour attendre que la coroutine se termine. De même, vous pouvez appeler son Annuler() méthode pour annuler immédiatement la coroutine.

En utilisant le lancement() la fonction est un peu comme créer un nouveau fil avec un Runnable objet, principalement parce que vous ne pouvez pas retourner de valeur. Si vous voulez pouvoir renvoyer une valeur de votre coroutine, vous devez la créer en utilisant le async () fonction à la place.

le async () la fonction retourne un Différé objet, qui, tout comme le Emploi objet, vous permet de gérer la coroutine. Cependant, il vous permet également d’utiliser le attendre() fonction d'attendre le résultat de la coroutine sans bloquer le thread en cours.

Par exemple, considérons les coroutines suivantes qui utilisent le fetchWebsiteContents () en suspendant la fonction et en renvoyant la longueur du contenu de deux adresses de page Web différentes:

val jobForLength1 = async fetchWebsiteContents ("https://webdesign.tutsplus.com") .length val jobForLength2 = async fetchWebsiteContents ("https://photography.tutsplus.com") .length

Avec le code ci-dessus, les deux coroutines démarreront immédiatement et fonctionneront en parallèle.

Si vous voulez maintenant utiliser les longueurs retournées, vous devez appeler le attendre() méthode à la fois Différé objets. Cependant, parce que le attendre() méthode est aussi une fonction de suspension, vous devez vous assurer que vous l'appelez à partir d'une autre coroutine.

Le code suivant montre comment calculer la somme des deux longueurs à l’aide d’une nouvelle coroutine créée avec le lancement() une fonction:

launch val sum = jobForLength1.await () + jobForLength2.await () println ("Téléchargé $ somme sur octets!")

7. Utilisation de Coroutines dans le fil de l'interface utilisateur

Les coroutines utilisent les threads d'arrière-plan en interne, c'est pourquoi elles ne s'exécutent pas par défaut sur le thread d'interface utilisateur d'une application Android. Par conséquent, si vous essayez de modifier le contenu de l'interface utilisateur de votre application depuis une coroutine, vous rencontrerez une erreur d'exécution. Heureusement, il est très facile de lancer une coroutine sur le fil de l’interface utilisateur: il suffit de passer le UI objet comme argument à votre constructeur de coroutine.

Par exemple, voici comment réécrire la dernière coroutine pour afficher la somme dans un Affichage widget:

launch (UI) val sum = jobForLength1.await () + jobForLength2.await () myTextView.text = "Téléchargé $ sum bytes!" 

Le code ci-dessus peut sembler banal au début, mais regardez encore. Il est non seulement capable d'attendre que deux opérations en arrière-plan soient terminées sans utiliser de rappels, il est également capable de le faire sur le thread d'interface utilisateur de l'application sans le bloquer.!

Avoir la possibilité d'attendre sur le thread d'interface utilisateur, sans que votre interface utilisateur ne semble lente ni déclencher une erreur Application Not Responding, souvent appelée ANR, simplifie de nombreuses tâches autrement complexes.. 

Par exemple, avec la suspension retard() fonction, qui est l'équivalent non bloquant de la Thread.sleep () méthode, vous pouvez maintenant créer des animations avec des boucles. Pour vous aider à démarrer, voici un exemple de coroutine qui incrémente la coordonnée x d’un Affichage widget toutes les 400 ms, créant ainsi un effet de style:

lancer (UI) while (myTextView.x < 800)  myTextView.x += 10 delay(400)  

Conclusion

Lors du développement d'applications Android, il est impératif d'effectuer des opérations de longue durée dans les threads d'arrière-plan. Dans ce tutoriel, vous avez appris différentes approches à suivre pour créer et gérer de tels threads dans Kotlin. Vous avez également appris à utiliser la fonction de coroutines encore expérimentale pour attendre dans les threads sans les bloquer..

Pour en savoir plus sur les coroutines, vous pouvez vous référer à la documentation officielle. Et pendant que vous êtes ici, consultez certains de nos autres articles sur Kotlin et le développement Android.!