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.
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)
}
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:
val a = a()
b()
, atteignant suspendCoroutine
.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.a()
, qui voit cette valeur de retour, la renvoie également.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.
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.