Solución:
Echaría un vistazo a esto: http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html, es bastante interesante y debería proporcionar un buen lugar para comenzar. Pero, por supuesto, estamos usando Java para que podamos hacerlo mejor (o tal vez peor porque no hay macros :))
Según mi entendimiento, con las corrutinas, por lo general, tiene un productor y un consumidor coroutine (o al menos este es el patrón más común). Pero semánticamente no quiere que el productor llame al consumidor o viceversa porque esto introduce una asimetría. Pero dada la forma en que funcionan los lenguajes basados en pilas, necesitaremos que alguien haga la llamada.
Así que aquí hay una jerarquía de tipos muy simple:
public interface CoroutineProducer<T>
{
public T Produce();
public boolean isDone();
}
public interface CoroutineConsumer<T>
{
public void Consume(T t);
}
public class CoroutineManager
{
public static Execute<T>(CoroutineProducer<T> prod, CoroutineConsumer<T> con)
{
while(!prod.IsDone()) // really simple
{
T d = prod.Produce();
con.Consume(d);
}
}
}
Ahora, por supuesto, la parte difícil es implementar las interfaces, en particular, es difícil dividir un cálculo en pasos individuales. Para esto probablemente querrías un conjunto completo de estructuras de control persistentes. La idea básica es que queremos simular una transferencia de control no local (al final, es como si estuviéramos simulando una goto
). Básicamente, queremos dejar de usar la pila y la pc
(programa-contador) manteniendo el estado de nuestras operaciones actuales en el montón en lugar de en la pila. Por lo tanto, necesitaremos un montón de clases auxiliares.
Por ejemplo:
Digamos que en un mundo ideal querías escribir un consumidor que luciera así (psuedocode):
boolean is_done;
int other_state;
while(!is_done)
{
//read input
//parse input
//yield input to coroutine
//update is_done and other_state;
}
necesitamos abstraer la variable local como is_done
y other_state
y necesitamos abstraer el bucle while en sí mismo porque nuestro yield
como la operación no va a utilizar la pila. Así que creemos una abstracción de bucle while y clases asociadas:
enum WhileState {BREAK, CONTINUE, YIELD}
abstract class WhileLoop<T>
{
private boolean is_done;
public boolean isDone() { return is_done;}
private T rval;
public T getReturnValue() {return rval;}
protected void setReturnValue(T val)
{
rval = val;
}
public T loop()
{
while(true)
{
WhileState state = execute();
if(state == WhileState.YIELD)
return getReturnValue();
else if(state == WhileState.BREAK)
{
is_done = true;
return null;
}
}
}
protected abstract WhileState execute();
}
El truco básico aquí es moverse local variables a ser clase variables y convertir los bloques de alcance en clases, lo que nos da la capacidad de ‘volver a ingresar’ en nuestro ‘bucle’ después de obtener nuestro valor de retorno.
Ahora para implementar nuestro productor
public class SampleProducer : CoroutineProducer<Object>
{
private WhileLoop<Object> loop;//our control structures become state!!
public SampleProducer()
{
loop = new WhileLoop()
{
private int other_state;//our local variables become state of the control structure
protected WhileState execute()
{
//this implements a single iteration of the loop
if(is_done) return WhileState.BREAK;
//read input
//parse input
Object calcluated_value = ...;
//update is_done, figure out if we want to continue
setReturnValue(calculated_value);
return WhileState.YIELD;
}
};
}
public Object Produce()
{
Object val = loop.loop();
return val;
}
public boolean isDone()
{
//we are done when the loop has exited
return loop.isDone();
}
}
Se podrían realizar trucos similares para otras estructuras de flujo de control básicas. Lo ideal sería crear una biblioteca de estas clases auxiliares y luego usarlas para implementar estas interfaces simples que finalmente le darían la semántica de las co-rutinas. Estoy seguro de que todo lo que he escrito aquí se puede generalizar y ampliar enormemente.
Sugeriría mirar las corrutinas de Kotlin en JVM. Sin embargo, cae en una categoría diferente. No hay manipulación de códigos de bytes involucrada y también funciona en Android. Sin embargo, tendrás que escribir tus corrutinas en Kotlin. La ventaja es que Kotlin está diseñado para la interoperabilidad con Java en mente, por lo que aún puede continuar usando todas sus bibliotecas de Java y combinar libremente el código de Kotlin y Java en el mismo proyecto, incluso colocándolos uno al lado del otro en los mismos directorios y paquetes.
Esta Guía de kotlinx.coroutines proporciona muchos más ejemplos, mientras que el documento de diseño de corrutinas explica toda la motivación, los casos de uso y los detalles de implementación.
Kotlin utiliza el siguiente enfoque para las co-rutinas
(de https://kotlinlang.org/docs/reference/coroutines.html):
Las corrutinas se implementan completamente a través de una técnica de compilación (no se requiere soporte desde el lado de la máquina virtual o del sistema operativo), y la suspensión funciona a través de la transformación del código. Básicamente, cada función de suspensión (se pueden aplicar optimizaciones, pero no entraremos en esto aquí) se transforma en una máquina de estado donde los estados corresponden a llamadas en suspensión. Justo antes de una suspensión, el siguiente estado se almacena en un campo de una clase generada por el compilador junto con las variables locales relevantes, etc. Tras la reanudación de esa corrutina, las variables locales se restauran y la máquina de estados procede del estado inmediatamente después de la suspensión.
Una corrutina suspendida se puede almacenar y pasar como un objeto que mantiene su estado y locales suspendidos. El tipo de tales objetos es Continuación, y la transformación de código general descrita aquí corresponde al estilo clásico de Continuación-paso. En consecuencia, las funciones de suspensión toman un parámetro adicional de tipo Continuación bajo el capó.
Consulte el documento de diseño en https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md