Threads vs Coroutines à Kotlin

Fils vs Coroutines à Kotlin

1. introduction

Dans ce rapide didacticiel, nous allons créer et exécuter des threads dans Kotlin.

Plus tard, nous discuterons de la façon de l’éviter complètement, en faveur deKotlin Coroutines.

2. Création de threads

Créer un thread dans Kotlin est similaire à le faire en Java.

Nous pouvons soit étendre la classeThread (bien que cela ne soit pas recommandé car Kotlin ne prend pas en charge l'héritage multiple):

class SimpleThread: Thread() {
    public override fun run() {
        println("${Thread.currentThread()} has run.")
    }
}

Ou nous pouvons implémenter l'interfaceRunnable:

class SimpleRunnable: Runnable {
    public override fun run() {
        println("${Thread.currentThread()} has run.")
    }
}

Et de la même manière que nous le faisons en Java, nous pouvons l'exécuter en appelant la méthodestart():

val thread = SimpleThread()
thread.start()

val threadWithRunnable = Thread(SimpleRunnable())
threadWithRunnable.start()

Alternativement, comme Java 8, Kotlin prend en chargeSAM Conversions, nous pouvons donc en profiter et passer un lambda:

val thread = Thread {
    println("${Thread.currentThread()} has run.")
}
thread.start()

2.2. Fonction Kotlinthread()

Une autre façon est de considérer la fonctionthread() que Kotlin fournit:

fun thread(
  start: Boolean = true,
  isDaemon: Boolean = false,
  contextClassLoader: ClassLoader? = null,
  name: String? = null,
  priority: Int = -1,
  block: () -> Unit
): Thread

Avec cette fonction, un thread peut être instancié et exécuté simplement par:

thread(start = true) {
    println("${Thread.currentThread()} has run.")
}

La fonction accepte cinq paramètres:

  • start – Pour exécuter immédiatement le thread

  • isDaemon - Pour créer le thread en tant que thread démon

  • contextClassLoader - Un chargeur de classe à utiliser pour charger des classes et des ressources

  • name – Pour définir le nom du thread

  • priority - Pour définir la priorité du thread

3. Kotlin Coroutines

Il est tentant de penser que la création de plus de threads peut nous aider à exécuter plus de tâches simultanément. Malheureusement, ce n’est pas toujours vrai.

Créer trop de threads peut en réalité rendre une application sous-performante dans certaines situations; Les threads sont des objets qui imposent une surcharge lors de l'allocation d'objets et de la récupération de place.

Pour surmonter ces problèmes,Kotlin introduced a new way of writing asynchronous, non-blocking code; the Coroutine.

Semblables aux threads, les coroutines peuvent être exécutées simultanément, attendre et communiquer les unes avec les autres, à la différence que leur création est bien moins chère que les threads.

3.1. Contexte Coroutine

Avant de présenter les constructeurs de coroutine fournis immédiatement par Kotlin, nous devons discuter du contexte de Coroutine.

Les coroutines s'exécutent toujours dans un contexte constitué d'un ensemble d'éléments divers.

Les principaux éléments sont:

  • Job - modélise un workflow annulable avec plusieurs états et un cycle de vie qui culmine dans son achèvement

  • Dispatcher - détermine le ou les threads que la coroutine correspondante utilise pour son exécution. Avec le répartiteur,we can a confine coroutine execution to a specific thread, dispatch it to a thread pool, or let it run unconfined

Nous verrons comment spécifier le contexte pendant que nous décrivons les coroutines dans les étapes suivantes.

3.2. launch

Lelaunch function is a coroutine builder which starts a new coroutine without blocking the current thread et renvoie une référence à la coroutine en tant qu'objetJob:

runBlocking {
    val job = launch(Dispatchers.Default) {
        println("${Thread.currentThread()} has run.")
    }
}

Il a deux paramètres optionnels:

  • context - Le contexte dans lequel la coroutine est exécutée, s'il n'est pas défini, il hérite du contexte desCoroutineScope à partir desquels il est lancé

  • start - Les options de démarrage de la coroutine. Par défaut, la coroutine est immédiatement planifiée pour exécution

Notez que le code ci-dessus est exécuté dans un pool d'arrière-plan partagé de threads car nous avons utiliséDispatchers.Default w qui le lance dans leGlobalScope.

Alternativement, nous pouvons utiliserGlobalScope.launch qui utilise le même répartiteur:

val job = GlobalScope.launch {
    println("${Thread.currentThread()} has run.")
}

Lorsque nous utilisonsDispatchers.Default ouGlobalScope.launch, nous créons une coroutine de premier niveau. Même s'il est léger, il consomme quand même quelques ressources de mémoire pendant son exécution.

Au lieu de lancer des coroutines dans lesGlobalScope, comme nous le faisons habituellement avec les threads (les threads sont toujours globaux), nous pouvons lancer des coroutines dans le cadre spécifique de l'opération que nous effectuons:

runBlocking {
    val job = launch {
        println("${Thread.currentThread()} has run.")
    }
}

Dans ce cas, nous commençons une nouvelle coroutine dans le constructeur de scoroutinerunBlocking (que nous décrirons plus tard) sans spécifier le contexte. Ainsi, la coroutine héritera du contexte derunBlocking.

3.3. async

Une autre fonction fournie par Kotlin pour créer une coroutine estasync.

La fonctionasync crée une nouvelle coroutine et renvoie un résultat futur sous la forme d'une instance deDeferred<T>:

val deferred = async {
    [email protected] "${Thread.currentThread()} has run."
}

deferred est un futur annulable non bloquant qui décrit un objet qui agit comme un proxy pour un résultat initialement inconnu.

Commelaunch,, nous pouvons spécifier un contexte dans lequel exécuter la coroutine ainsi qu'une option de démarrage:

val deferred = async(Dispatchers.Unconfined, CoroutineStart.LAZY) {
    println("${Thread.currentThread()} has run.")
}

Dans ce cas, nous avons lancé la coroutine en utilisant lesDispatchers.Unconfined qui démarre les coroutines dans le thread appelant mais seulement jusqu'au premier point de suspension.

Notez queDispatchers.Unconfined convient bien lorsqu'une coroutine ne consomme pas de temps CPU ni ne met à jour les données partagées.

In addition, Kotlin provides Dispatchers.IO that uses a shared pool of on-demand created threads:

val deferred = async(Dispatchers.IO) {
    println("${Thread.currentThread()} has run.")
}

Dispatchers.IO est recommandé lorsque nous devons effectuer des opérations d'E / S intensives.

3.4. runBlocking

Nous avons déjà examinérunBlocking, mais parlons-en plus en détail.

runBlocking est une fonction qui exécute une nouvelle coroutine et bloque le thread actuel jusqu'à son achèvement.

À titre d'exemple, dans l'extrait précédent, nous avons lancé la coroutine mais nous n'avons jamais attendu le résultat.

Pour attendre le résultat, nous devons appeler la méthode de suspension deawait():

// async code goes here

runBlocking {
    val result = deferred.await()
    println(result)
}

await()  est ce qu'on appelle une fonction de suspension. Suspend functions are only allowed to be called from a coroutine or another suspend function. Pour cette raison, nous l'avons inclus dans une invocation derunBlocking.

Nous utilisonsrunBlocking dans les fonctionsmain et dans les tests afin de pouvoir lier le code de blocage à d'autres écrits en style de suspension.

De la même manière que dans d'autres constructeurs de coroutine, nous pouvons définir le contexte d'exécution:

runBlocking(newSingleThreadContext("dedicatedThread")) {
    val result = deferred.await()
    println(result)
}

Notez que nous pouvons créer un nouveau thread dans lequel nous pourrions exécuter la coroutine. Cependant, un fil dédié est une ressource coûteuse. Et, lorsque vous n'en avez plus besoin, nous devrions le publier ou même mieux le réutiliser tout au long de l'application.

4. Conclusion

Dans ce tutoriel, nous avons appris à exécuter du code asynchrone non bloquant en créant un thread.

Comme alternative au fil, nous avons également vu comment l'approche de Kotlin pour utiliser les coroutines est simple et élégante.

Comme d'habitude, tous les exemples de code présentés dans ce didacticiel sont disponiblesover on Github.