Vamos a romper hacia abajo en algo más simple:
async static void Go()
{
await Something();
Go();
await SomethingElse();
}
¿Cómo trata el compilador con esto?
Básicamente esto se convierte en algo parecido a este bosquejo:
class HelperClass
{
private State state = STARTSTATE;
public void DoIt()
{
if (state == STARTSTATE) goto START;
if (state == AFTERSOMETHINGSTATE) goto AFTERSOMETHING;
if (state == AFTERSOMETHINGELSESTATE) goto AFTERSOMETHINGELSE;
START:
{
state = AFTERSOMETHINGSTATE;
var awaiter = Something().MakeAnAwaiter();
awaiter.WhenDoneDo(DoIt);
return;
}
AFTERSOMETHING:
{
Go();
state = AFTERSOMETHINGELSESTATE;
var awaiter = SomethingElse().MakeAnAwaiter();
awaiter.WhenDoneDo(DoIt);
return;
}
AFTERSOMETHINGELSE:
return;
}
static void Go()
{
var helper = new HelperClass();
helper.DoIt();
}
Ahora todo lo que tiene que recordar es que cuando cada operación asincrónica, "DoIt" está programado para ser llamado de nuevo por el bucle de mensajes (en la apropiada instancia del ayudante, por supuesto).
¿Qué sucede? Resolverlo. Llamas a Go por primera vez. Eso hace que el asistente sea el número uno y llame a DoIt. Eso llama a Something(), recupera una tarea, hace una espera para esa tarea, le dice al que espera "cuando hayas terminado, llama a helper1.DoIt" y regresa.
Una décima de segundo más tarde la tarea se completa y el bucle de mensaje llama al DoIt de helper1. El estado de helper1 es AFTERSOMETHINGSTATE, así que tomamos el goto y llamamos a Go. Eso hace que helper2 y llame a DoIt sobre eso. Eso llama a Something(), recupera una tarea, hace una espera para esa tarea, le dice al que espera "cuando hayas terminado, llama a DoIt en helper2" y devuelve el control a DoIt de helper1. Eso llama a SomethingElse, hace que alguien lo aguarde y le dice "cuando termines de hacer otra cosa, llama a doit de helper1". Luego regresa.
Ahora tenemos dos tareas pendientes y ningún código en la pila. Una de las tareas se completará primero. Supongamos que la tarea SomethingElse se completa primero. El bucle de mensaje llama a helper1.DoIt(), que devuelve inmediatamente. Helper1 ahora es basura.
Más tarde, el bucle de mensaje llama a helper2.DoIt() y se ramifica a AFTERSOMETHING. Ahora se llama a Go(), que crea helper3 ...
Así que no, no hay recursividad ilimitada aquí. Cada vez que Go se ejecuta, se ejecuta hasta que se inicie asincrónicamente Something() y luego vuelve a su llamador. La llamada a las cosas después de "algo" sucede más tarde. "Ir" solo está en la pila una vez a la vez.
Bueno. Lo sospeché mucho, pero es bueno ver la mecánica de la construcción en detalle. Gracias por una respuesta tan completa (¡como de costumbre!). – spender
@spender: ¡De nada! Disfrute del CTP y, si tiene preguntas, comentarios, inquietudes, elogios, críticas constructivas, etc., publíquelos en el Foro Async CTP. Tenemos gerentes de programa que lo leen todos los días y reúnen los comentarios de los usuarios sobre la función que es muy útil para nosotros. –