async-await - corutinas - hilos en kotlin
¿Qué significa la función suspender en Kotlin Coroutine? (3)
Estoy leyendo Kotlin Coroutine y sé que se basa en la función de suspend
. ¿Pero qué significa suspend
?
¿Coroutine o función se suspende?
Desde https://kotlinlang.org/docs/reference/coroutines.html
Básicamente, las corrutinas son cálculos que pueden suspenderse sin bloquear un hilo.
He oído a la gente decir a menudo "suspender la función". Pero creo que es la coroutina la que se suspende porque está esperando a que termine la función. "suspender" por lo general significa "cesar la operación", en este caso el coroutine está inactivo.
🤔 ¿Debemos decir que la coroutina está suspendida?
¿Qué coroutine se suspende?
Desde https://kotlinlang.org/docs/reference/coroutines.html
Para continuar con la analogía, await () puede ser una función de suspensión (por lo tanto, también se puede llamar desde dentro de un bloque asíncrono {}) que suspende una rutina hasta que se realiza algún cálculo y devuelve su resultado:
async { // Here I call it the outer async coroutine
...
// Here I call computation the inner coroutine
val result = computation.await()
...
}
Dice "eso suspende una coroutine hasta que se realiza algún cálculo", pero la coroutine es como un hilo liviano. Entonces, si se suspende la coroutina, ¿cómo se realiza el cálculo?
Vemos que se llama await en el computation
, por lo que podría ser async
que devuelve Deferred
, lo que significa que puede iniciar otra rutina.
fun computation(): Deferred<Boolean> {
return async {
true
}
}
🤔 La cita dice que suspende una coroutina . ¿Significa suspend
la rutina externa async
o suspend
la computation
interna?
suspend
significa que, mientras la coroutina externa async
está esperando ( await
) a que finalice la computation
interna para computation
, ésta (la coroutina async
externa) está inactiva (de ahí el nombre de suspensión) y devuelve el hilo a la agrupación de hilos, y cuando finaliza la computation
secundaria coroutine , ¿(la coroutina async
externa) se despierta, toma otro hilo de la piscina y continúa?
La razón por la que menciono el hilo es debido a https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html
El hilo se devuelve a la piscina mientras el coroutine está esperando, y cuando se hace la espera, el coroutine se reanuda en un hilo libre en el pool
¿Coroutine o función se suspende?
Llamar a una función de suspensión suspende la rutina, lo que significa que el hilo actual comienza a ejecutar otra conexión. Entonces, se dice que la coroutina está suspendida.
Pero técnicamente, tanto la función como la rutina se detienen, porque se suspende la ejecución del código de su función, en el contexto de la línea actual. Esto se debe a que su función no será ejecutada por otra persona en ese momento. Pero estamos dividiendo los pelos aquí.
¿Qué coroutine se suspende?
La async
exterior inicia una coroutina. Cuando llama a computation()
, el async
interno inicia una segunda rutina. Luego, la llamada a await()
suspende la ejecución de la coroutine async
externa, hasta que finaliza la ejecución de la coroutine async
interna .
Incluso puedes ver eso con un solo hilo: el hilo ejecutará el comienzo del async
externo, luego llamará computation()
y alcanzará el async
interno. En este punto, el cuerpo del async interno se omite y el hilo continúa ejecutando el async
externo hasta que llega a la await()
. await()
es un "punto de suspensión", porque await
es una función de suspensión. Esto significa que la coroutina externa está suspendida, y así el hilo comienza a ejecutar la interna. Cuando se hace, vuelve a ejecutar el final de la async
externa.
Suspender significa que, mientras la coroutina externa asíncrona está esperando (esperando) a que finalice la computación interna para computación, ésta (la coroutina asíncrona externa) está inactiva (de ahí el nombre de suspensión) y devuelve el hilo a la agrupación de hilos, y cuando finaliza la computación secundaria coroutine , ¿(la coroutina asíncrona externa) se despierta, toma otro hilo de la piscina y continúa?
Sí, precisamente.
Como herramienta de aprendizaje, le sugiero que vaya a través de este código, que expone el mecanismo básico que subyace en todas las construcciones de conveniencia, como 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
}
}
}
El despachador de Unconfined
Unconfined básicamente elimina la magia del despacho de coroutine: el código dentro del bloque de launch
simplemente comienza a ejecutarse como parte de la llamada de launch
. Lo que pasa es lo siguiente:
- Evaluar
val a = a()
- Esto encadena a
b()
, llegando asuspendCoroutine
. - La función
b()
ejecuta el bloque pasado asuspendCoroutine
y luego devuelve un valor especialCOROUTINE_SUSPENDED
. Este valor no es observable a través del modelo de programación de Kotlin, pero eso es lo que hace el método Java compilado. - La función
a()
, al ver este valor de retorno, también lo devuelve. - El bloque de
launch
hace lo mismo y el control ahora vuelve a la línea después de la invocación delaunch
:10.downTo(0)...
Tenga en cuenta que, en este punto, tendrá el mismo efecto que si el código dentro del bloque de launch
y su fun main
código fun main
se ejecutaran simultáneamente. Simplemente sucede que todo esto está sucediendo en un solo hilo nativo, por lo que el bloque de launch
está "suspendido".
Ahora, dentro del código de bucle forEach
, el programa lee la continuation
que escribió la función b()
y la resumes
con el valor de 10
. resume()
se implementa de tal manera que será como si la llamada suspendCoroutine
regresara con el valor que usted suspendCoroutine
. Así que de repente se encuentra en medio de ejecutar b()
. El valor que pasó para resume()
se asigna a i
y se compara con 0
. Si no es cero, el bucle while (true)
continúa dentro de b()
, nuevamente llegando a suspendCoroutine
, momento en el cual su llamada de resume()
regresa, y ahora pasa por otro paso de bucle en forEach()
. Esto continúa hasta que finalmente se reanuda con 0
, luego se println
instrucción println
y el programa se completa.
El análisis anterior debería darle la importante intuición de que "suspender una coroutine" significa devolver el control a la invocación de launch
más interna (o, más generalmente, al constructor de coroutine ). Si un coroutine se suspende nuevamente después de reanudar, la llamada resume()
finaliza y el control regresa al interlocutor de resume()
.
La presencia de un despachador de coroutine hace que este razonamiento sea menos claro porque la mayoría de ellos envían inmediatamente su código a otro hilo. En ese caso, la historia anterior ocurre en ese otro hilo, y el despachador de Coroutine también administra el objeto de continuation
para que pueda reanudarlo cuando el valor de retorno esté disponible.
Las funciones de suspensión están en el centro de todo lo relacionado con las rutinas. Una función de suspensión es simplemente una función que se puede pausar y reanudar posteriormente. Pueden ejecutar una operación de larga duración y esperar a que se complete sin bloqueo.
La sintaxis de una función de suspensión es similar a la de una función normal, excepto por la adición de la palabra clave de suspensión. Puede tomar un parámetro y tener un tipo de retorno. Sin embargo, las funciones de suspensión solo pueden ser invocadas por otra función de suspensión o dentro de una rutina.
suspend fun backgroundTask(param: Int): Int {
// long running operation
}
Bajo el capó, las funciones de suspensión son convertidas por el compilador a otra función sin la palabra clave de suspensión, que toma un parámetro de adición de tipo Continuación. La función anterior, por ejemplo, será convertida por el compilador a esto:
fun backgroundTask(param: Int, callback: Continuation<Int>): Int {
// long running operation
}
La continuación es una interfaz que contiene dos funciones que se invocan para reanudar la rutina con un valor de retorno o con una excepción si se produjo un error mientras se suspendía la función.
interface Continuation<in T> {
val context: CoroutineContext
fun resume(value: T)
fun resumeWithException(exception: Throwable)
}