Programación Asíncrona con asyncio
En aplicaciones modernas que realizan muchas operaciones de red o entrada/salida (I/O), como consultar APIs, descargar archivos o interactuar con bases de datos, esperar de forma síncrona a que cada operación termine bloquea el hilo principal y desperdicia recursos.
asyncio es el módulo estándar de Python para escribir programas concurrentes utilizando la sintaxis async / await basada en un Bucle de Eventos (Event Loop).
1. El Bucle de Eventos (Event Loop) y Corrutinas #
- Corrutina: Una función declarada con
async def. Al llamarla no se ejecuta inmediatamente, sino que devuelve un objeto corrutina. await: Pausa la ejecución de la corrutina actual cediendo el control al bucle de eventos para que otras tareas avancen mientras se espera una operación asíncrona.asyncio.run(): Punto de entrada para iniciar el bucle de eventos.
1import asyncio 2 3async def consultar_servicio(nombre: str, segundos: float) -> str: 4 print(f"[{nombre}] Iniciando consulta...") 5 await asyncio.sleep(segundos) # Simula una operación I/O asíncrona no bloqueante 6 print(f"[{nombre}] ¡Respuesta recibida!") 7 return f"Datos de {nombre}" 8 9async def main(): 10 resultado = await consultar_servicio("API_Usuarios", 1.0) 11 print(f"Resultado: {resultado}") 12 13# Ejecutar el bucle de eventos 14asyncio.run(main())
2. Ejecución Concurrente con asyncio.gather() #
asyncio.gather() permite disparar múltiples corrutinas en paralelo y esperar a que todas finalicen:
1import asyncio 2import time 3 4async def descargar_recurso(id_recurso: int) -> str: 5 await asyncio.sleep(0.5) # Simula descarga de 500ms 6 return f"Recurso_{id_recurso}" 7 8async def main(): 9 inicio = time.perf_counter() 10 11 # Ejecuta 3 descargas concurrentemente 12 resultados = await asyncio.gather( 13 descargar_recurso(1), 14 descargar_recurso(2), 15 descargar_recurso(3), 16 ) 17 18 tiempo_total = time.perf_counter() - inicio 19 print(f"Resultados: {resultados}") 20 print(f"Tiempo total: {tiempo_total:.2f} s") # ~0.50 s en lugar de 1.50 s síncronos 21 22asyncio.run(main())Salida de consola Resultados: ['Recurso_1', 'Recurso_2', 'Recurso_3'] Tiempo total: 0.50 s
3. Manejo Estructurado con asyncio.TaskGroup (Python 3.11+) #
TaskGroup es la forma moderna y recomendada para gestionar tareas concurrentes (Structured Concurrency). Si una tarea falla, cancela automáticamente las demás del grupo y propaga las excepciones usando ExceptionGroup:
1import asyncio 2 3async def tarea(id_tarea: int, demora: float): 4 await asyncio.sleep(demora) 5 print(f"Tarea {id_tarea} completada") 6 7async def main(): 8 async with asyncio.TaskGroup() as tg: 9 t1 = tg.create_task(tarea(1, 0.2)) 10 t2 = tg.create_task(tarea(2, 0.1)) 11 t3 = tg.create_task(tarea(3, 0.3)) 12 print("Todas las tareas del grupo han concluido de forma segura") 13 14asyncio.run(main())Salida de consola Tarea 2 completada Tarea 1 completada Tarea 3 completada Todas las tareas del grupo han concluido de forma segura
4. Gestores de Contexto e Iteradores Asíncronos #
Python soporta gestión de contexto e iteración asíncrona mediante async with y async for:
1# Gestor de contexto asíncrono (ejemplo conceptual para conexiones HTTP/BBDD) 2class ConexionAsincrona: 3 async def __aenter__(self): 4 print("Abriendo conexión asíncrona...") 5 await asyncio.sleep(0.1) 6 return self 7 8 async def __aexit__(self, exc_type, exc_val, exc_tb): 9 print("Cerrando conexión asíncrona...") 10 await asyncio.sleep(0.1) 11 12async def demo(): 13 async with ConexionAsincrona(): 14 print("Operando dentro del contexto asíncrono...") 15 16asyncio.run(demo())
Resumen del tema
Conceptos clave #
async def/await: sintaxis nativa para definir y esperar corrutinas sin bloquear el hilo principal.- Bucle de Eventos (Event Loop): orquestador que gestiona la ejecución y el cambio de contexto entre tareas en espera de I/O.
asyncio.gather(*corrutinas): ejecuta y recolecta los resultados de múltiples corrutinas concurrentes.asyncio.TaskGroup(Python 3.11+): estándar moderno de concurrencia estructurada con cancelación segura y captura de errores conExceptionGroup.async with/async for: soporte para protocolos asíncronos en recursos y flujos de datos continuos.
Qué debes recordar #
Usa asyncio para maximizar el rendimiento en operaciones intensivas en I/O (APIs, microservicios, bases de datos) estructurando tus tareas con async with asyncio.TaskGroup().