Saltar al contenido

¿Cómo funcionan las corrutinas de Kotlin internamente?

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 funs. 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)

  1. 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.

  1. A (A), Kotlin comienza a ejecutar la corrutina en el siguiente hilo libre disponible (Diga Thread01).

También puede ser el mismo hilo al que llamaste launch.

  1. A (B), Kotlin deja de ejecutar el hilo actual e inicia la función de suspensión loadData() 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.

  1. 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.

  1. (C) se ejecuta en Thread03.
  2. A (D), los Thread03 está parado.
  3. Después de 1000 ms, (E) se ejecuta en el siguiente hilo libre, digamos Thread01.

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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *