web-dev-qa-db-fra.com

Que signifie la fonction de suspension dans Kotlin Coroutine

Je lis Kotlin Coroutine et je sais qu'il est basé sur la fonction suspend. Mais que signifie suspend

Coroutine ou fonction est-elle suspendue?

De https://kotlinlang.org/docs/reference/coroutines.html

Fondamentalement, les coroutines sont des calculs qui peuvent être suspendus sans bloquer un thread

J'ai entendu des gens dire souvent "suspendre la fonction". Mais je pense que c'est la coroutine qui est suspendue parce qu'elle attend la fin de la fonction? "suspendre" signifie généralement "cesser l'opération", dans ce cas la coroutine est inactive.

???? Faut-il dire que la coroutine est suspendue?

Quelle coroutine est suspendue?

De https://kotlinlang.org/docs/reference/coroutines.html

Pour continuer l'analogie, wait () peut être une fonction de suspension (donc également appelable depuis un bloc async {{})) qui suspend un coroutine jusqu'à ce qu'un calcul soit effectué et renvoie son résultat:

async { // Here I call it the outer async coroutine
    ...
    // Here I call computation the inner coroutine
    val result = computation.await()
    ...
}

???? Il dit "cela suspend une coroutine jusqu'à ce que le calcul soit terminé", mais la coroutine est comme un fil léger. Donc, si la coroutine est suspendue, comment le calcul peut-il être effectué?

Nous voyons que await est appelé sur computation, de sorte qu'il pourrait être async qui renvoie Deferred, ce qui signifie qu'il peut démarrer une autre coroutine

fun computation(): Deferred<Boolean> {
    return async {
        true
    }
}

???? La citation dit qui suspend une coroutine}. Est-ce que cela signifie suspend la coroutine async extérieure ou suspend la coroutine computation?

suspend signifie-t-il que pendant que la _async_ coroutine extérieure attend (await) attend la fin de la coroutine computation intérieure, elle (la _async coroutine extérieure) tourne au ralenti (d'où le nom suspend) et renvoie le fil dans le pool de threads, puis lorsque la _computation-enfant finit , il (la coroutine async extérieure) se réveille, prend un autre fil de la piscine et continue?

La raison pour laquelle je mentionne le fil est à cause de https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html

Le thread est renvoyé dans le pool pendant que la coroutine est en attente et, lorsque l'attente est terminée, la coroutine reprend sur un thread libre du pool.

25
onmyway133

Fonctions de suspension sont au centre de tout les coroutines . Une fonction de suspension est simplement une fonction qui peut être suspendue et reprise ultérieurement. Ils peuvent exécuter une opération longue et attendre la fin de l'opération sans blocage.

La syntaxe d'une fonction en suspension est similaire à celle d'une fonction normale, à l'exception de l'ajout du mot-clé suspend. Il peut prendre un paramètre et avoir un type de retour. Cependant, les fonctions en suspension ne peuvent être appelées que par une autre fonction en suspension ou dans une coroutine.

suspend fun backgroundTask(param: Int): Int {
     // long running operation
}

Sous le capot, les fonctions de suspension sont converties par le compilateur en une autre fonction sans le mot-clé suspend, qui prend un paramètre d'addition de type Continuation. La fonction ci-dessus par exemple, sera convertie par le compilateur en ceci:

fun backgroundTask(param: Int, callback: Continuation<Int>): Int {
   // long running operation
}

La suite est une interface qui contient deux fonctions appelées pour reprendre la coroutine avec une valeur de retour ou avec une exception si une erreur s’est produite pendant la suspension de la fonction.

interface Continuation<in T> {
   val context: CoroutineContext
   fun resume(value: T)
   fun resumeWithException(exception: Throwable)
}
15

En tant qu’outil d’apprentissage, je vous suggère de consulter ce code, qui expose le mécanisme de base sous-jacent à toutes les constructions de commodité telles que async:

import kotlinx.coroutines.experimental.Unconfined
import kotlinx.coroutines.experimental.launch
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.suspendCoroutine

var continuation: Continuation<Int>? = null

fun main(args: Array<String>) {
    launch(Unconfined) {
        val a = a()
        println("Result is $a")
    }
    10.downTo(0).forEach {
        continuation!!.resume(it)
    }
}

suspend fun a(): Int {
    return b()
}

suspend fun b(): Int {
    while (true) {
        val i = suspendCoroutine<Int> { cont -> continuation = cont }
        if (i == 0) {
            return 0
        }
    }
}

Le répartiteur Unconfined de coroutine élimine fondamentalement la magie de la répartition de coroutine: le code à l'intérieur du bloc launch commence simplement à s'exécuter dans le cadre de l'appel launch. Ce qui se passe est comme suit:

  1. Evaluer val a = a()
  2. Ceci enchaîne à b(), atteignant suspendCoroutine.
  3. La fonction b() exécute le bloc passé à suspendCoroutine et renvoie ensuite une valeur spéciale COROUTINE_SUSPENDED. Cette valeur n'est pas visible dans le modèle de programmation Kotlin, mais c'est ce que fait la méthode Java compilée.
  4. La fonction a(), qui voit cette valeur de retour, la renvoie également.
  5. Le bloc launch fait de même et le contrôle revient maintenant à la ligne après l'invocation launch: 10.downTo(0)...

Notez que, à ce stade, vous avez le même effet que si le code à l'intérieur du bloc launch et votre code fun main s'exécutaient simultanément. Il se trouve que tout cela se passe sur un seul thread natif, le bloc launch est donc "suspendu".

Désormais, dans le code de boucle forEach, le programme lit la continuation que la fonction b() a écrite et resumes avec la valeur 10. resume() est implémenté de manière à ce que l'appel suspendCoroutine soit renvoyé avec la valeur que vous avez transmise. Vous vous retrouvez donc tout à coup en train d'exécuter b(). La valeur que vous avez transmise à resume() est affectée à i et vérifiée avec 0. Si ce n'est pas zéro, la boucle while (true) continue à l'intérieur de b(), atteignant à nouveau suspendCoroutine, moment auquel l'appel resume() est renvoyé et vous passez maintenant à une autre étape de la boucle dans forEach(). Cela continue jusqu'à ce que vous ayez finalement repris 0, puis l'instruction println est exécutée et le programme terminé.

L'analyse ci-dessus devrait vous donner l'intuition importante que "suspendre une coroutine" signifie renvoyer le contrôle à l'invocation la plus interne launch (ou, plus généralement, coroutine builder ). Si une coroutine est suspendue à nouveau après la reprise, l'appel resume() prend fin et le contrôle est renvoyé à l'appelant de resume().

La présence d'un répartiteur de coroutine rend ce raisonnement moins clair, car la plupart d'entre eux soumettent immédiatement votre code à un autre thread. Dans ce cas, l'histoire ci-dessus se produit dans cet autre thread et le répartiteur de coroutine gère également l'objet continuation afin qu'il puisse le reprendre lorsque la valeur de retour est disponible.

7
Marko Topolnik

Coroutine ou fonction est suspendue?

Appeler une fonction de suspension ing suspendre le s la coroutine, ce qui signifie que le thread actuel peut commencer à exécuter une autre coroutine. Ainsi, la coroutine est suspendue plutôt que la fonction.

Mais techniquement, votre fonction ne sera pas exécutée par une autre coroutine à ce moment-là. Nous pouvons donc dire que la fonction et la coroutine s'arrêtent, mais nous coupons les cheveux en quatre.

Quelle coroutine est suspendue?

La variable async extérieure commence une coroutine. Quand il appelle computation(), la async intérieure commence une seconde coroutine. Ensuite, l'appel à await() suspend l'exécution de la coroutine outerasync, jusqu'à ce que l'exécution de la coroutine innerasync soit terminée.

Vous pouvez même voir cela avec un seul thread: le thread exécutera le début de la async extérieure, puis appellera computation() et atteindra la async intérieure. À ce stade, le corps de l'async interne est ignoré et le thread continue d'exécuter la async extérieure jusqu'à atteindre await().await() est un "point de suspension", car await est une fonction suspendue . la coroutine externe est suspendue et le thread commence donc à exécuter la interne. Quand c'est fait, il revient pour exécuter la fin de la async extérieure.

Suspendre signifie que pendant que la coroutine async externe attend la fin du coroutine de calcul interne, elle (la coroutine async externe) se met au repos (d'où le nom suspendu) et renvoie le thread dans le pool de threads, puis lorsque la coroutine de calcul enfant termine , il (la coroutine async externe) se réveille, prend un autre thread de la piscine et continue?

Oui, justement.

2
Joffrey