Luego de consultar especialistas en la materia, programadores de diversas ramas y profesores hemos dado con la solución al dilema y la plasmamos en este post.
Solución:
Las corrutinas son algo completamente independiente de cualquier política de programación que describa. Una corrutina es básicamente una cadena de llamadas de suspend fun
s. La suspensión está totalmente bajo tu control: solo tienes que llamar suspendCoroutine
. Obtendrá un objeto de devolución de llamada para que pueda llamar a su resume
método y volver a donde suspendió.
Aquí hay un código donde puede ver que la suspensión es un mecanismo muy directo y transparente, totalmente bajo su control:
import kotlin.coroutines.*
import kotlinx.coroutines.*
var continuation: Continuation? = null
fun main(args: Array)
val job = GlobalScope.launch(Dispatchers.Unconfined)
while (true)
println(suspendHere())
continuation!!.resume("Resumed first time")
continuation!!.resume("Resumed second time")
suspend fun suspendHere() = suspendCancellableCoroutine
continuation = it
Todo el código anterior se ejecuta en el mismo hilo principal. No hay subprocesos múltiples en absoluto.
La corrutina tu launch
se suspende cada vez que llama suspendHere()
. Escribe la devolución de llamada de continuación en el continuation
propiedad, y luego usa explícitamente esa continuación para reanudar la corrutina.
El código usa el Unconfined
Despachador de corrutina que no envía ningún hilo a los subprocesos, simplemente ejecuta el código de corrutina justo allí donde invoca continuation.resume()
.
Con eso en mente, revisemos su diagrama:
GlobalScope.launch <---- (A)
val y = loadData() <---- (B) // suspend fun loadData()
println(y) <---- (C)
delay(1000) <---- (D)
println("completed") <---- (E)
- Kotlin tiene un predefinido
ThreadPool
al principio.
Puede tener o no un grupo de subprocesos. Un despachador de UI trabaja con un solo hilo.
El requisito previo para que un subproceso sea el objetivo de un despachador de rutina es que haya una cola concurrente asociada con él y el subproceso ejecute un bucle de nivel superior que toma Runnable
objetos de esta cola y los ejecuta. Un despachador de rutina simplemente coloca la continuación en esa cola.
- A
(A)
, Kotlin comienza a ejecutar la corrutina en el siguiente hilo libre disponible (DigaThread01
).
También puede ser el mismo hilo al que llamaste launch
.
- A
(B)
, Kotlin deja de ejecutar el hilo actual e inicia la función de suspensiónloadData()
en el siguiente hilo libre disponible (Thread02
).
Kotlin no necesita detener ningún hilo para suspender una corrutina. De hecho, el punto principal de las corrutinas es que los hilos no empezar o detener. El bucle de nivel superior del hilo continuará y seleccionará otro ejecutable para ejecutar.
Además, el mero hecho de que estés llamando a un suspend fun
no tiene ningún significado. La corrutina solo se suspenderá cuando llame explícitamente suspendCoroutine
. La función también puede simplemente regresar sin suspensión.
Pero supongamos que llamó suspendCoroutine
. En ese caso, la corrutina ya no se ejecuta en cualquier hilo. Está suspendido y no puede continuar hasta que algún código, en algún lugar, llame continuation.resume()
. Ese código podría ejecutarse en cualquier hilo, en cualquier momento en el futuro.
- Cuando
(B)
regresa después de la ejecución, Kotlin continúa la corrutina en el siguiente hilo gratuito disponible (Thread03
).
B
no "regresa después de la ejecución", la corrutina se reanuda mientras todavía está dentro de su cuerpo. Puede suspender y reanudar cualquier número de veces antes de regresar.
(C)
se ejecuta enThread03
.- A
(D)
, losThread03
está parado.- Después de 1000 ms,
(E)
se ejecuta en el siguiente hilo libre, digamosThread01
.
Nuevamente, no se detiene ningún hilo. La corrutina se suspende y se utiliza un mecanismo, generalmente específico del despachador, para programar su reanudación después de 1000 ms. En ese momento, se agregará a la cola de ejecución asociada con el despachador.
Para mayor especificidad, veamos algunos ejemplos de qué tipo de código se necesita para enviar una corrutina.
Despachador de interfaz de usuario de swing:
EventQueue.invokeLater continuation.resume(value)
Despachador de IU de Android:
mainHandler.post continuation.resume(value)
Despachador ExecutorService:
executor.submit continuation.resume(value)
Las corrutinas funcionan creando un cambio sobre posibles puntos de reanudación:
class MyClass$Coroutine extends CoroutineImpl
public Object doResume(Object o, Throwable t)
switch(super.state)
default:
throw new IllegalStateException("call to "resume" before "invoke" with coroutine");
case 0:
// code before first suspension
state = 1; // or something else depending on your branching
break;
case 1:
...
return null;
El código resultante que ejecuta esta corrutina crea esa instancia y llama al doResume()
cada vez que necesita reanudar la ejecución, la forma en que se maneja depende del planificador utilizado para la ejecución.
Aquí hay una compilación de ejemplo para una corrutina simple:
launch
println("Before")
delay(1000)
println("After")
Que compila a este bytecode
private kotlinx.coroutines.experimental.CoroutineScope p$;
public final java.lang.Object doResume(java.lang.Object, java.lang.Throwable);
Code:
0: invokestatic #18 // Method kotlin/coroutines/experimental/intrinsics/IntrinsicsKt.getCOROUTINE_SUSPENDED:()Ljava/lang/Object;
3: astore 5
5: aload_0
6: getfield #22 // Field kotlin/coroutines/experimental/jvm/internal/CoroutineImpl.label:I
9: tableswitch // 0 to 1
0: 32
1: 77
default: 102
32: aload_2
33: dup
34: ifnull 38
37: athrow
38: pop
39: aload_0
40: getfield #24 // Field p$:Lkotlinx/coroutines/experimental/CoroutineScope;
43: astore_3
44: ldc #26 // String Before
46: astore 4
48: getstatic #32 // Field java/lang/System.out:Ljava/io/PrintStream;
51: aload 4
53: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
56: sipush 1000
59: aload_0
60: aload_0
61: iconst_1
62: putfield #22 // Field kotlin/coroutines/experimental/jvm/internal/CoroutineImpl.label:I
65: invokestatic #44 // Method kotlinx/coroutines/experimental/DelayKt.delay:(ILkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
68: dup
69: aload 5
71: if_acmpne 85
74: aload 5
76: areturn
77: aload_2
78: dup
79: ifnull 83
82: athrow
83: pop
84: aload_1
85: pop
86: ldc #46 // String After
88: astore 4
90: getstatic #32 // Field java/lang/System.out:Ljava/io/PrintStream;
93: aload 4
95: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
98: getstatic #52 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
101: areturn
102: new #54 // class java/lang/IllegalStateException
105: dup
106: ldc #56 // String call to 'resume' before 'invoke' with coroutine
108: invokespecial #60 // Method java/lang/IllegalStateException."":(Ljava/lang/String;)V
111: athrow
Compilé esto con kotlinc 1.2.41
De 32 a 76 es el código para imprimir Before
y llamando delay(1000)
que suspende.
Del 77 al 101 es el código para imprimir After
.
De 102 a 111 es el manejo de errores para estados de reanudación ilegales, como lo indica el default
etiqueta en la tabla de interruptores.
Entonces, como resumen, las corrutinas en kotlin son simplemente máquinas de estado que son controladas por algún programador.
Puntuaciones y reseñas
Nos puedes auxiliar nuestro trabajo dejando un comentario y dejando una puntuación te damos la bienvenida.