Está bien, aquí está mi opinión.
Hay algo llamado coroutines que se conoce desde hace décadas. ("Knuth and Hopper" -class "durante décadas") Son generalizaciones de subrutinas , en las que no solo obtienen y liberan el control en la función de inicio y retorno de la función, sino que también lo hacen en puntos específicos ( puntos de suspensión ). Una subrutina es una coroutina sin puntos de suspensión.
Son sencillos de implementar con macros C, como se muestra en el siguiente documento sobre "protothreads". ( enlace ) Léalo. Voy a esperar ...
La conclusión de esto es que las macros crean un gran switch
y una etiqueta case
en cada punto de suspensión. En cada punto de suspensión, la función almacena el valor de la etiqueta case
inmediatamente siguiente, para que sepa dónde reanudar la ejecución la próxima vez que se llame. Y devuelve el control a la persona que llama.
Esto se hace sin modificar el flujo aparente de control del código descrito en el "protothread".
Imagina ahora que tienes un gran bucle que llama a todos estos "protothreads" a su vez, y obtienes simultáneamente la ejecución de "protothreads" en un solo hilo.
Este enfoque tiene dos inconvenientes:
- No puede mantener el estado en las variables locales entre las reanudaciones.
- No puede suspender el "protothread" de una profundidad de llamada arbitraria. (todos los puntos de suspensión deben estar en el nivel 0)
Hay soluciones para ambos:
- Todas las variables locales deben ajustarse al contexto del protothread (contexto que ya es necesario por el hecho de que el protothread debe almacenar su próximo punto de reanudación)
- Si siente que realmente necesita llamar a otro protothread desde un protothread, "genere" un protothread infantil y suspenda hasta que el niño termine.
Y si tuvieras soporte de compilador para hacer el trabajo de reescritura que hacen las macros y la solución, bueno, simplemente puedes escribir el código de tu protothread tal como pretendes e insertar puntos de suspensión con una palabra clave.
Y esto es de lo que se trata async
y await
: crear coroutines (sin pila).
Las coroutinas en C # se reifican como objetos de la clase (genérica o no genérica) Task
.
Encuentro estas palabras clave muy confusas. Mi lectura mental es:
-
async
como "suspensible"
-
await
como "suspender hasta que se complete"
-
Task
como "futuro ..."
Ahora. ¿Realmente necesitamos marcar la función async
? Además de decir que debería desencadenar los mecanismos de reescritura de código para hacer que la función sea una rutina, resuelve algunas ambigüedades. Considera este código.
public Task<object> AmIACoroutine() {
var tcs = new TaskCompletionSource<object>();
return tcs.Task;
}
Suponiendo que async
no es obligatorio, ¿es esto una rutina o una función normal? ¿Debería el compilador reescribirlo como un coroutine o no? Ambos podrían ser posibles con diferentes semánticas eventuales.