200 votes

Que signifie la fonction de suspension dans Kotlin Coroutine

Je suis en train de lire Kotlin Coroutine et de savoir qu'il est basé sur suspend fonction. Mais qu'est - suspend moyenne?

Coroutine ou la fonction est suspendu?

À partir 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 les gens disent souvent "fonction de suspension". Mais je pense que c'est la coroutine qui est suspendu, car il est en attente pour la fonction de fini? "suspendre" signifie généralement "cesser de fonctionner", dans ce cas, la coroutine est inactif.

218voto

La suspension de fonctions sont au centre de tout, coroutines. Une suspension de fonction est simplement une fonction qui peut être mis en pause et de le reprendre ultérieurement. Ils peuvent exécuter une opération longue à exécuter et attendez qu'elle se termine sans blocage.

La syntaxe d'une suspension de fonction est similaire à celle d'une fonction régulière, sauf pour l'ajout de la suspendre mot-clé. Il peut prendre un paramètre et avoir un type de retour. Toutefois, la suspension de fonctions ne peut être invoquée que par un autre suspension de fonction ou à l'intérieur d'une coroutine.

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

Sous le capot, de suspendre les fonctions sont convertis par le compilateur à l'autre de la fonction sans la suspendre mot-clé, qui prend un ajout paramètre de type de Poursuite . La fonction ci-dessus par exemple, sera convertie par le compilateur à ceci:

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

La poursuite est une interface qui contient deux fonctions qui sont appelées à reprendre la coroutine avec une valeur de retour ou avec une exception si une erreur s'est produite pendant que la fonction a été suspendu.

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

58voto

Joffrey Points 6838

Coroutine ou la fonction est suspendu?

L'appel d'une suspensioning fonction suspendres la coroutine, sens le thread actuel peut lancer l'exécution d'un autre coroutine. Ainsi, la coroutine est dit être suspendu plutôt que de la fonction.

Mais techniquement, votre fonction ne sera pas exécuté par un autre coroutine à ce point, on peut donc dire qu'à la fois la fonction et la coroutine arrêter, mais nous sommes fractionnement de poils ici.

Qui coroutine est suspendu?

L'extérieur async commence une coroutine. Lorsqu'il appelle computation(), à l'intérieur async commence un deuxième coroutine. Ensuite, l'appel à l' await() suspend l'exécution de l' extérieur async coroutine, jusqu'à l'exécution de l' intérieur asyncs'coroutine est plus.

Vous pouvez même voir qu'avec un seul fil: le fil d'exécuter l'extérieur async's début, puis appelez computation() et de rejoindre le centre - async. À ce stade, le corps de l'intérieur de la async est ignoré, et le fil continue l'exécution à l'extérieur async jusqu'à ce qu'il atteigne await(). await() est un "point de suspension", car await est une suspension de fonction. Cela signifie que l'extérieur coroutine est suspendu, et donc le thread démarre l'exécution de l'intérieur. Quand c'est fait, on en revient à exécuter à la fin de l'extérieur, async.

N'suspendre dire que, tout en externe asynchrone coroutine est en attente (attendre) pour la partie calcul coroutine à la fin, il (l'externe asynchrone coroutine) tourne au ralenti (d'où le nom de suspendre) et renvoie thread du pool de threads, et quand l'enfant calcul coroutine est terminée, il (l'externe asynchrone coroutine) se réveille, prend un autre thread de la piscine et continue?

Oui, justement.

42voto

Marko Topolnik Points 77257

Pour comprendre exactement ce que cela signifie pour suspendre une coroutine, je vous suggère d'aller par le biais de ce code:

import kotlinx.coroutines.Dispatchers.Unconfined
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

var continuation: Continuation<Int>? = null

fun main() = runBlocking {
    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
        }
    }
}

L' Unconfined coroutine répartiteur élimine la magie de la coroutine expédition et nous permet de nous concentrer directement sur les nus coroutines.

Le code à l'intérieur de l' launch bloc commence à s'exécuter directement sur le thread en cours, comme une partie de l' launch appel. Ce qui se passe est comme suit:

  1. Évaluer val a = a()
  2. Cette chaînes d' b(), atteignant suspendCoroutine.
  3. La fonction b() exécute le bloc transmis suspendCoroutine , puis renvoie un spécial COROUTINE_SUSPENDED de la valeur. Cette valeur n'est pas observable par le biais de la Kotlin modèle de programmation, mais c'est ce que la compilation Java méthode.
  4. La fonction a(), en voyant cette valeur de retour, elle aussi, le renvoie.
  5. L' launch bloc fait la même chose et contrôle maintenant les retours à la ligne après l' launch invocation: 10.downTo(0)...

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

Maintenant, à l'intérieur de l' forEach code de boucle, le programme lit l' continuation que l' b() fonction a écrit et resumes avec la valeur de 10. resume() est appliquée de telle manière qu'il sera comme si l' suspendCoroutine appel retourné avec la valeur passée dans. Si vous vous trouvez soudainement dans le milieu de l'exécution d' b(). La valeur de vous passé de resume() est affectée à l' i et comparés 0. Si ce n'est pas zéro, l' while (true) boucle se déroule à l'intérieur d' b(), de nouveau, atteignant suspendCoroutine, à quel point votre resume() retours d'appel, et maintenant, vous allez par le biais d'une autre boucle de l'étape en forEach(). Cela continue jusqu'à ce que finalement vous reprendre avec 0, puis l' println instruction est exécutée et le programme se termine.

L'analyse ci-dessus devrait vous donner l'importante intuition que "la suspension d'un coroutine" signifie retourner le contrôle de retour à l'intime launch invocation (ou, plus généralement, de la coroutine builder). Si une coroutine suspend de nouveau après la reprise, l' resume() la fin de l'appel et le contrôle retourne à l'appelant d' resume().

La présence d'une coroutine répartiteur fait ce raisonnement, à moins claire, car la plupart d'entre eux soumettre immédiatement votre code à un autre thread. Dans ce cas, le ci-dessus l'histoire se passe dans l'autre thread, et la coroutine répartiteur gère également l' continuation objet de sorte qu'il peut le reprendre lorsque la valeur de retour est disponible.

10voto

J'ai trouvé que la meilleure façon de comprendre suspend est de faire une analogie entre this mot-clé et coroutineContext de la propriété.

Kotlin fonctions peuvent être déclarés comme local ou global. Les fonctions locales comme par magie, ont accès à l' this mot clé global n'en ont pas.

Kotlin fonctions peuvent être déclarés comme suspend ou de blocage. suspend fonctions comme par magie, ont accès à l' coroutineContext de la propriété tout en bloquant les fonctions ne font pas.

Le truc, c'est: coroutineContextde la propriété est déclaré comme "normal" de la propriété dans Kotlin stdlib mais cette déclaration est juste un stub de la documentation/les fins de navigation. En fait, coroutineContext est builtin propriété intrinsèque cela signifie que sous le capot, le compilateur de la magie au courant de cette propriété comme la conscience de la langue, mots-clés.

Donc, vous avez besoin d' suspend pour obtenir un accès à l' coroutineContext de la propriété de l'instance en cours d'exécution coroutine contexte

10voto

j2emanue Points 3456

Je voulais vous donner un exemple simple de la notion de continuation. C'est ce qu'une fonction de suspension ne peut geler, de suspendre puis il continue/reprise. Arrêter de penser de la coroutine en termes de threads et un Sémaphore. Pensez-y en termes de maintien et même le rappel des crochets.

Pour être clair, une courtine peut être mis en pause à l'aide d'un suspect fonction. permet d'étudier cette question:

dans android, nous pourrions le faire par exemple:

var TAG = "myTAG:"
        fun myMethod() {
            viewModelScope.launch(Dispatchers.Default) {
                for (i in 10..15) {
                    if (i == 10) { //on first iteration, we will completely FREEZE this coroutine (just for loop here gets 'suspended`)
                        println("$TAG im a tired coroutine - let someone else print the numbers async. i'll suspend until your done")
                        freezePleaseIAmDoingHeavyWork()
                    } else
                        println("$TAG $i")
                }
            }

            //this area is not suspended, you can continue doing work
        }


        suspend fun freezePleaseIAmDoingHeavyWork() {
            withContext(Dispatchers.Default) {
                async {
                    //pretend this is a big network call
                    for (i in 1..10) {
                        println("$TAG $i")
                        delay(1_000)//delay pauses coroutine, NOT the thread. use  Thread.sleep if you want to pause a thread. 
                    }
                    println("$TAG phwww finished printing those numbers async now im tired, thank you for freezing, you may resume")
                }
            }
        }

qui imprime le suivant:

I: myTAG: my coroutine is frozen but i can carry on to do other things

I: myTAG: im a tired coroutine - let someone else print the numbers async. i'll suspend until your done

I: myTAG: 1
I: myTAG: 2
I: myTAG: 3
I: myTAG: 4
I: myTAG: 5
I: myTAG: 6
I: myTAG: 7
I: myTAG: 8
I: myTAG: 9
I: myTAG: 10

I: myTAG: phwww finished printing those numbers async now im tired, thank you for freezing, you may resume

I: myTAG: 11
I: myTAG: 12
I: myTAG: 13
I: myTAG: 14
I: myTAG: 15

imaginez qu'il fonctionne comme ceci:

enter image description here

si la fonction que vous lancé de ne pas s'arrêter, juste une coroutine va suspendre pendant qu'elle continue. le fil n'est pas suspendue par l'exécution d'une fonction de suspension.

je pense que ce site peut aider à vous tout de choses et c'est ma référence.

permet de faire quelque chose de cool et de geler notre fonction de suspension au moyen d'une itération. nous allons reprendre plus tard dans onResume:

stocker une variable appelée continuation et bien le charger avec les coroutines la poursuite de l'objet pour nous:

var continuation: CancellableContinuation<String>? = null

suspend fun freezeHere() = suspendCancellableCoroutine<String> {
            continuation = it
        }

 fun unFreeze(){
            continuation?.resume("im resuming") {}
        }

maintenant, revenons à notre fonction de suspension et d'en faire geler la mi itération:

 suspend fun freezePleaseIAmDoingHeavyWork() {
        withContext(Dispatchers.Default) {
            async {
                //pretend this is a big network call
                for (i in 1..10) {
                    println("$TAG $i")
                    delay(1_000)
                    if(i == 3)
                        freezeHere() //dead pause, do not go any further
                }

            }
        }
    }

puis ailleurs, comme dans onResume (par exemple):

override fun onResume() {
        super.onResume()
        unFreeze()
    }

et la boucle continue. son assez agréable de savoir qu'on peut congeler une fonction de suspension à tout moment et le reprendre après un certain temps a passé. vous pouvez aussi regarder les chaînes

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X