PythonAprende PythonDocumentación

Asyncio

Corrutinas, async/await y concurrencia cooperativa.

asyncio es concurrencia en un solo hilo: un event loop que coordina miles de tareas que esperan I/O (red, disco). Es el motor detrás de FastAPI, aiohttp y todo el ecosistema asíncrono de Python.

La idea: nunca esperar ocioso

Imagina un mesero (el event loop) y diez mesas (tus tareas). Un mesero síncrono atiende una mesa de principio a fin. El mesero asíncrono toma la orden de la mesa 1, la pasa a la cocina (await), y mientras la cocina trabaja, atiende la mesa 2. Nunca está parado esperando.

async def y await

import asyncio

async def preparar_cafe():           # esto es una CORRUTINA
    print("moliendo...")
    await asyncio.sleep(2)           # pausa NO bloqueante: cede el control
    print("café listo")
    return "espresso"

async def main():
    resultado = await preparar_cafe()   # esperar sin bloquear el loop
    print(resultado)

asyncio.run(main())   # el punto de entrada: arranca el event loop

Detalles que confunden a todos al principio:

  • Llamar preparar_cafe() no ejecuta nada: devuelve un objeto coroutine
  • await es el que la ejecuta (y pausa hasta que termine)
  • await asyncio.sleep(2) simula espera — a diferencia de time.sleep(2), que bloquearía todo el loop
  • Solo puedes await dentro de funciones async def

Concurrencia real: gather

import asyncio

async def tarea(nombre, segundos):
    await asyncio.sleep(segundos)
    print(f"{nombre} terminó")
    return nombre.upper()

async def main():
    inicio = asyncio.get_event_loop().time()

    resultados = await asyncio.gather(
        tarea("A", 2),
        tarea("B", 1),
        tarea("C", 3),
    )

    total = asyncio.get_event_loop().time() - inicio
    print(resultados)          # ['A', 'B', 'C'] → en orden de llamada
    print(f"{total:.0f}s")     # ~3s (la más larga), NO 6s

asyncio.run(main())

gather corre las tres tareas concurrentemente: 2+1+3 segundos de espera se solapan y el total es el de la más lenta. Esto es la magia de asyncio.

La regla de oro: no bloquear el loop

import time

async def mal():
    time.sleep(5)          # BLOQUEA todo el loop: ninguna tarea avanza

async def bien():
    await asyncio.sleep(5)  # cede el control: otras tareas corren

Una sola llamada bloqueante (time.sleep, requests, cálculo pesado) congela TODAS tus tareas. Para librerías de I/O usa las versiones async (aiohttp, no requests).

Tareas: el nivel siguiente

asyncio.create_task lanza una corrutina "en el fondo" sin esperarla de inmediato:

async def main():
    tarea = asyncio.create_task(tarea("X", 2))   # empieza YA
    print("hago otras cosas mientras...")         # corre mientras X espera
    resultado = await tarea                       # aquí sí espero el resultado
    print(resultado)

Y para trabajo pesado de CPU dentro de un mundo async: await asyncio.to_thread(funcion_bloqueante) lo manda a un hilo.

¿asyncio o threading?

Criterioasynciothreading
Miles de conexionesExcelente (muy ligero)Inviable (miles de hilos)
Código de libreríasNecesita ecosistema asyncUsa librerías normales
Complejidad mentalAlta al inicioRiesgos de carrera

Para servicios web con muchas conexiones lentas (APIs, websockets), asyncio es el rey. Es el modelo mental de FastAPI.

Resumen

  • async def define corrutinas; await las ejecuta cediendo el control
  • asyncio.run(main()) es el punto de entrada
  • gather corre tareas concurrentes: el total es la tarea más lenta, no la suma
  • Nunca bloquees el loop: asyncio.sleep, no time.sleep; librerías async, no bloqueantes
  • asyncio brilla con MUCHAS esperas de I/O; para CPU, procesos

Quiz

  1. 1. ¿Qué es una corrutina (async def)?

  2. 2. ¿Cuándo acelera asyncio tu programa?

  3. 3. ¿Qué hace asyncio.gather(tarea1(), tarea2())?

Ejercicios

Ejercicio 1: Primera corrutina

Define `async def saludo()` que imprima hola async, y un main async que la ejecute con await. Lanza main con asyncio.run().

Cargando editor…

Ejercicio 2: Concurrente con gather

Dos corrutinas devuelven 1 y 2. Usa asyncio.gather en main para imprimirlas juntas como lista. Resultado: [1, 2]

Cargando editor…