DMarlon Studio

Lección 19 · 19 de 20

asyncio con async y await · Python

Programación asíncrona en Python con asyncio: corrutinas con async y await, gather para tareas a la vez, sleep(0) y wait_for con tiempo límite.

Python · asyncio con async y await en DMarlon Studio: el código del ejemplo y su salida en la terminal

Una función con `async def` es una corrutina: al llamarla no se ejecuta, sino que devuelve un objeto que el bucle de eventos de asyncio irá ejecutando. asyncio.run(main()) crea ese bucle, ejecuta main() y lo cierra al acabar.

`await` espera a otra corrutina y, mientras tanto, cede el turno. check_stock simula una consulta a un almacén con await asyncio.sleep(...): mientras Madrid «tarda», el bucle atiende a Bilbao y a Sevilla. Por eso las tres preguntas salen seguidas y las respuestas llegan por orden de rapidez: Bilbao (0,01 s), Sevilla y Madrid. Todo tarda lo que la más lenta, no la suma.

`asyncio.gather()` lanza varias corrutinas a la vez y espera a todas. Aunque terminen en otro orden, devuelve los resultados en el orden en que se pasaron, así que results empieza por Madrid.

await asyncio.sleep(0) no espera nada: solo devuelve el control al bucle. Con él, packer y printer se turnan en cada trabajo. Ese es el modelo de asyncio: un solo hilo y cambios de tarea solo en los await, de modo que el orden es predecible.

`asyncio.wait_for(..., timeout=0.05)` pone un tiempo límite: la operación de un segundo se cancela y salta TimeoutError, que el programa captura. asyncio brilla con muchas esperas de red o de disco; para cálculos pesados no aporta velocidad.

Código · main.py

import asyncio# Three warehouses answer a stock query; each one takes a different timeDELAY = {"Madrid": 0.03, "Bilbao": 0.01, "Sevilla": 0.02}STOCK = {"Madrid": 12, "Bilbao": 0, "Sevilla": 7}async def check_stock(city, product):    print(f"asking {city} about {product}...")    await asyncio.sleep(DELAY[city])         # waits without blocking the rest    print(f"  {city} answered")    return city, STOCK[city]async def worker(name, jobs):    for job in jobs:        print(f"{name}: {job}")        await asyncio.sleep(0)               # hand control back to the loopasync def main():    # gather runs them at the same time and keeps the order of the results    results = await asyncio.gather(*(check_stock(c, "chair") for c in DELAY))    print("Results:", results)    # Two coroutines taking turns: each await is a switch point    await asyncio.gather(worker("packer", ["box #1", "box #2"]),                         worker("printer", ["label #1", "label #2"]))    try:                                     # give up if it takes too long        await asyncio.wait_for(asyncio.sleep(1), timeout=0.05)    except TimeoutError:        print("Slow supplier: timed out")asyncio.run(main())

Salida · python3 main.py

asking Madrid about chair...
asking Bilbao about chair...
asking Sevilla about chair...
  Bilbao answered
  Sevilla answered
  Madrid answered
Results: [('Madrid', 12), ('Bilbao', 0), ('Sevilla', 7)]
packer: box #1
printer: label #1
packer: box #2
printer: label #2
Slow supplier: timed out

Para recordar

  • async def define una corrutina; await espera sin bloquear.
  • asyncio.run() arranca el bucle de eventos.
  • gather ejecuta varias a la vez y mantiene el orden de los resultados.
  • Las tareas solo se alternan en los await.
  • wait_for cancela lo que pasa del tiempo límite.

Ejercicio

Añada un cuarto almacén, Valencia, con 0,005 s de retraso y 3 unidades, y compruebe en qué posición responde y en cuál aparece en results.