2.32. Corrutinas

Una corrutina es una función que puede pausarse a mitad de su ejecución y más tarde reanudarse desde donde se quedó, con todas sus variables locales intactas. Refleja el patrón de los generadores – un cuerpo de función que puede suspenderse y reanudarse – con un cambio en quién impulsa cada reanudación.

La sintaxis de Python para escribir una corrutina es el par de palabras clave async / await. async marca la función como una corrutina; await marca los puntos en su interior donde se permite la pausa.

2.32.1. Definir una corrutina

Una función definida con async def es una función corrutina. Llamarla no ejecuta el cuerpo; devuelve un objeto corrutina que aún no ha comenzado:

async def greet(name):
    print("hello,", name)

coro = greet("Alice")        # body NOT run yet

El objeto corrutina queda pausado al principio mismo de la función. Algo tiene que impulsarlo para que el cuerpo se ejecute – ese algo es un bucle de eventos, un componente del entorno de ejecución. MicroPython incluye uno en el módulo asyncio. Por ahora, considera la corrutina como «lista para ejecutarse, a la espera de un impulsor».

2.32.2. Pausar dentro de una corrutina

Dentro de una corrutina, una expresión await suspende la ejecución hasta que el valor esperado esté listo:

async def fetch_and_log():
    data = await read_sensor()
    print("got:", data)

Cuando el cuerpo llega a await read_sensor(), la corrutina devuelve el control a lo que sea que la esté ejecutando. Cuando read_sensor() termina, el impulsor reanuda la corrutina en la línea siguiente, con el resultado vinculado a data.

await solo es válido dentro de una corrutina. Usarlo en una función normal es un error de sintaxis.

2.32.3. Relación con los generadores

Las corrutinas y los generadores comparten el mismo mecanismo subyacente. La diferencia está en quién provoca cada reanudación:

  • Un generador produce valores; el consumidor obtiene el siguiente con next() o iterando.

  • Una corrutina cede el control; un bucle de eventos programa la reanudación cuando la operación esperada está lista.

Si entiendes el intercambio del yield de los generadores, el de las corrutinas es la misma idea – solo que impulsado por un bucle de eventos en lugar de por un bucle for.

Un bucle de eventos es un pequeño despachador que mantiene una lista de corrutinas a la espera de algo (un temporizador, un evento de red, la finalización de otra corrutina). En cada iteración elige una corrutina cuya espera se ha satisfecho, la reanuda hasta el siguiente await, luego registra qué está esperando ahora esa corrutina y pasa a otra que esté lista. El resultado son muchas tareas avanzando de forma concurrente en un solo hilo – cada corrutina cede voluntariamente el control en sus puntos await, y el bucle aprovecha esos momentos con cualquier otra corrutina que esté lista para avanzar.

Internamente, await y yield usan la misma característica del entorno de ejecución de Python para suspender y reanudar una función. Las palabras clave difieren porque la convención en torno a ellas también difiere: yield devuelve un valor a un consumidor que lo obtiene con next(); await entrega el control a un bucle de eventos que programa la reanudación cuando la operación esperada está lista. async / await es esencialmente una sintaxis más reciente para el patrón de corrutinas – las bibliotecas más antiguas construían corrutinas directamente sobre la maquinaria de los generadores, usando yield from (presentado en Iteradores y generadores) para delegar la suspensión entre corrutinas.

2.32.4. Las corrutinas necesitan un impulsor

Una corrutina es inerte sin un entorno de ejecución que la impulse. Definir una está bien; ejecutarla requiere un bucle de eventos. El módulo asyncio de MicroPython proporciona ese bucle de eventos. La sección Asyncio explica cómo iniciar el bucle, programar corrutinas en él, compartir estado entre ellas con cerrojos y eventos, gestionar la cancelación y los tiempos de espera, y dar forma a una aplicación real en torno a las palabras clave async / await presentadas aquí.