¿Cómo podría implementarse la nueva característica de asincronización en c # 5.0 con call / cc?

He estado siguiendo el nuevo anuncio sobre la nueva función de async que estará en c # 5.0. Tengo un conocimiento básico del estilo de paso de continuación y de la transformación que hace el nuevo comstackdor de c # para codificar como este fragmento de la publicación de Eric Lippert :

 async void ArchiveDocuments(List urls) { Task archive = null; for(int i = 0; i < urls.Count; ++i) { var document = await FetchAsync(urls[i]); if (archive != null) await archive; archive = ArchiveAsync(document); } } 

Sé que algunos lenguajes implementan continuaciones de forma nativa, a través de call-with-current-continuation ( callcc ), pero realmente no entiendo cómo funciona eso o qué hace exactamente.

Entonces esta es la pregunta: si Anders et al. Decidió callcc y simplemente implementar callcc en c # 5.0 en lugar del caso especial async / callcc , ¿cómo se vería el fragmento de arriba?

Respuesta original:

Su pregunta, tal como lo entiendo, es “¿qué pasaría si en lugar de implementar” esperar “específicamente para una asincronía basada en tareas, en lugar de ello, se hubiera implementado la operación de flujo de control más general de llamada-con-continuación-actual?”

Bueno, antes que nada, pensemos en lo que “espera”. “aguardar” toma una expresión de tipo Task , obtiene un awaiter y llama al awaiter con la continuación actual:

 await FooAsync() 

se convierte efectivamente

 var task = FooAsync(); var awaiter = task.GetAwaiter(); awaiter.BeginAwait(somehow get the current continuation); 

Ahora supongamos que tenemos un operador callcc que toma como argumento un método y llama al método con la continuación actual. Eso se vería así:

 var task = FooAsync(); var awaiter = task.GetAwaiter(); callcc awaiter.BeginAwait; 

En otras palabras:

 await FooAsync() 

no es más que

 callcc FooAsync().GetAwaiter().BeginAwait; 

Eso responde tu pregunta?


Actualización # 1:

Como señala un comentarista, la respuesta siguiente asume el patrón de generación de código de la versión “Technology Preview” de la función async / await. De hecho, generamos un código ligeramente diferente en la versión beta de la función, aunque lógicamente es lo mismo. El codegen presente es algo así como:

 var task = FooAsync(); var awaiter = task.GetAwaiter(); if (!awaiter.IsCompleted) { awaiter.OnCompleted(somehow get the current continuation); // control now returns to the caller; when the task is complete control resumes... } // ... here: result = awaiter.GetResult(); // And now the task builder for the current method is updated with the result. 

Tenga en cuenta que esto es algo más complicado y maneja el caso en el que está “esperando” un resultado que ya se ha calculado. No hay necesidad de pasar por todo el rigamarole de ceder el control a la persona que llama y continuar de nuevo donde lo dejó si el resultado que está esperando ya está guardado en la memoria para usted allí mismo.

Por lo tanto, la conexión entre “esperar” y “callcc” no es tan sencilla como en el lanzamiento de la vista previa, pero todavía está claro que estamos esencialmente haciendo un callcc en el método “OnCompleted” del awaiter. Simplemente no hacemos callcc si no es necesario.


Actualización # 2:

Como esta respuesta

https://stackoverflow.com/a/9826822/88656

desde Timwi señala, la semántica de call / cc y await no es exactamente la misma; una llamada / cc “verdadera” requiere que “capturemos” la continuación completa de un método, incluida su stack de llamadas completa , o equivalentemente, que el progtwig completo se reescriba en el estilo de continuación de aprobación.

La función “esperar” se parece más a una “llamada cooperativa / cc”; la continuación solo captura “¿cuál es el método actual de devolución de tareas a punto de hacer en el momento de la espera?” Si la persona que llama del método de devolución de tareas va a hacer algo interesante después de completar la tarea, entonces es libre de registrar su continuación como la continuación de la tarea.

No soy un experto en continuaciones, pero intentaré explicar la diferencia entre async / await y call / cc. Por supuesto, esta explicación supone que entiendo call / cc y async / await, lo que no estoy seguro de hacer. Sin embargo, aquí va …

Con C # ‘async’, le está diciendo al comstackdor que genere una versión especial de ese método específico que comprende cómo embotellar su estado en una estructura de datos de stack, para que pueda “eliminarse de la stack real” y reanudarse más tarde. Dentro de un contexto asíncrono, “esperar” es como “llamar / cc” ya que usa los objetos generados por el comstackdor para embotellar el estado y salir de la “stack real” hasta que la tarea finalice. Sin embargo, debido a que es la reescritura del comstackdor de un método asíncrono que permite que el estado se retenga, esperar solo se puede usar dentro de un contexto asíncrono.

En call / cc de primera clase, el tiempo de ejecución de idioma genera todos los códigos de modo que la continuación actual se puede embotellar en una función de continuación de llamada (haciendo que la palabra clave asincrónica sea innecesaria). call / cc todavía actúa como espera, causando que el estado de continuación de la stream (piense en el estado de la stack) se embotelle y se transfiera como una función a la función llamada. Una forma de hacerlo es utilizar frames-heap para todas las invocación de funciones, en lugar de marcos de “stack”. (a veces denominado “sin astackmiento”, como en “python sin astackmiento” o muchas implementaciones de esquema) Otra forma es eliminar todos los datos de la “stack real” y rellenarlos en una estructura de datos del montón antes de llamar al objective call / cc .

Pueden surgir algunos problemas difíciles si hay llamadas a funciones externas (piense en DllImport) entremezcladas en la stack. Sospecho que esta es la razón por la que fueron con la implementación async / await.

http://www.madore.org/~david/computers/callcc.html


Debido a que en C #, una función debe marcarse como “asíncrona” para utilizar estas mecánicas, me pregunto si esta palabra clave asincrónica se convertirá en un virus que se propaga a muchas funciones en muchas bibliotecas. Si esto pasa. eventualmente se darán cuenta de que deben implementar call / cc de primera clase en el nivel VM, en lugar de este modelo asíncrono basado en la reescritura del comstackdor. Sólo el tiempo dirá. Sin embargo, es ciertamente una herramienta útil en el contexto del entorno actual de C #.

Un poco sin sentido diría. Los intérpretes de esquema a menudo implementan call / cc con una máquina de estados que captura el estado local en el montón. Que es exactamente lo que hace C # 5.0 (o más correctamente el iterador de C # 2.0) también. Implementaron call / cc, la abstracción que se les ocurrió es bastante elegante.