30 Reglas de Asincronía
Treinta reglas para escribir código asyncio correcto y de alto rendimiento en Python 3.14. Violar estas reglas causa errores sutiles que solo aparecen bajo carga.
Receta
Revisar al añadir
async defa cualquier módulo. Emparejar con pruebas de pytest-asyncio.
Cuándo recurrir a esto:
- Construyendo servicios FastAPI o workers asíncronos
- Migrando código síncrono a asyncio
- Depurando bloqueos del event loop o fugas de tareas
Profundización
Fundamentos del Event Loop (1-10)
- Un event loop por hilo - nunca llames a
asyncio.run()dentro de un loop en ejecución. - Usa
asyncio.run()como entrada principal - solo para el script de nivel superior y el runner de pruebas síncronas. - Prefiere
asyncio.TaskGroup(3.14) - concurrencia estructurada sobrecreate_taskbásico. - Haz
awaita todas las corrutinas - las corrutinas sinawaitemitenRuntimeWarningy no hacen nada. - No mezcles APIs síncronas y asíncronas -
requestsen un bloqueasync defbloquea el loop. - Usa
asyncio.to_thread()para I/O bloqueante - cuando no existe una librería asíncrona. - Instala pytest-asyncio -
asyncio_mode = "auto"en pyproject.toml. - Fixtures asíncronas para configuración asíncrona - conexiones de base de datos, clientes httpx.
- Cancela tareas al apagar - maneja
asyncio.CancelledErrorcon gracia. - Establece tiempos de espera en llamadas de red -
asyncio.wait_for()otimeout=en httpx.
Patrones de Concurrencia (11-20)
- asyncio para I/O, no para CPU - el trabajo de CPU va a
ProcessPoolExecutor. - Limita la concurrencia con Semaphore -
async with sem:limita las peticiones paralelas. - Usa
asyncio.gatherpara tareas independientes -return_exceptions=Truepara fallos parciales. - Usa
asyncio.Queuepara productor-consumidor - las colas acotadas previenen desbordamiento de memoria. - Evita el estado mutable compartido - pasa datos a través de argumentos; usa locks si es compartido.
- Los pools de conexión también son asíncronos - un
AsyncCliento pool por aplicación, no por petición. - Inicializa recursos asíncronos de forma perezosa en el lifespan - inicio de
lifespande FastAPI o@app.on_event. - Las tareas en segundo plano deben manejar errores - registra excepciones en tareas; no envíes y olvides silenciosamente.
- Prefiere gestores de contexto asíncronos -
async with session.begin():para límites de transacción. - Transmite respuestas grandes -
StreamingResponseen lugar de cargar el cuerpo completo en memoria.
Trampas y Depuración (21-30)
- Nunca uses
time.sleep()en async - usaawait asyncio.sleep(). - Cuidado con
@lru_cacheen funciones asíncronas - cachea objetos corrutina, no resultados. Usaasync_lru. - DNS es bloqueante - usa
aiodnso ejecuta el resolvedor en un hilo para aplicaciones de alta concurrencia. - El handshake SSL es bloqueante - usa clientes HTTP asíncronos (httpx, aiohttp), no urllib.
- Vigila las fugas de tareas - rastrea tareas en segundo plano; cancela al completar la petición.
- Prueba las rutas de cancelación - la desconexión del cliente debe cancelar el trabajo del servidor.
- Registra excepciones de tareas -
task.add_done_callbackpara registrar errores no manejados. - No reutilices loops cerrados - crea un loop nuevo por prueba o usa pytest-asyncio.
- Free-threaded 3.14 requiere auditoría - el estado compartido sin GIL requiere locks.
- Perfila bajo carga concurrente - el perfilado de una sola petición omite errores de contención.
Trampas Comunes
- Llamada bloqueante en ruta asíncrona - todo el servidor se detiene. Solución:
await asyncio.to_thread(blocking_fn). create_tasksin referencia - la tarea es recolectada como basura a mitad de ejecución. Solución: almacena la referencia de la tarea o usa TaskGroup.asyncio.gathersin manejo de errores - un fallo cancela todos. Solución:return_exceptions=Trueotry/exceptexplícito por tarea.- Cliente asíncrono global - conexiones obsoletas después de un fork. Solución: crea por proceso en el lifespan; cierra al apagar.
- Probar con
asyncio.runen cada prueba - lento y conflictos de loop. Solución: pytest-asyncio con modo automático.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| threading | Solo librerías bloqueantes | I/O de alta concurrencia |
| multiprocessing | Paralelismo intensivo en CPU | Se necesita estado compartido |
| FastAPI Síncrono + workers | Equipo no familiarizado con async | Intensivo en I/O con drivers asíncronos disponibles |
Preguntas Frecuentes
¿Cuándo debo usar async?
Cuando la aplicación está limitada por I/O y existen librerías asíncronas para tu base de datos, HTTP y caché.
¿FastAPI debe ser async?
No. Las rutas síncronas funcionan a través de un pool de hilos. Las rutas asíncronas son preferibles con drivers de base de datos asíncronos.
¿TaskGroup vs gather?
TaskGroup (3.11+) para concurrencia estructurada con cancelación automática en caso de fallo.
¿Cómo depuro un servidor congelado?
Usa py-spy sobre el proceso. Busca bloqueos síncronos en las pilas de llamadas asíncronas.
¿Patrón de sqlalchemy async?
async with async_session() as session: por petición a través de la dependencia de FastAPI.
¿Puede Django ser async?
Django 5.x soporta vistas asíncronas. El soporte async del ORM es parcial; evalúa por caso de uso.
¿Cuántas tareas concurrentes?
Empieza con un semáforo de 10-50. Ajusta según los límites de conexión y la memoria.
¿Qué pasa con trio?
anyio hace de puente entre asyncio y trio. El ecosistema de FastAPI es nativo de asyncio.
¿Generadores async?
async for con generadores asíncronos. Cierra con aclose() en bloques finally.
¿Cómo migro de síncrono a asíncrono?
Un endpoint a la vez. Cambia el driver síncrono por uno asíncrono. Prueba bajo carga.
Relacionado
- Asincronía y Concurrencia para Rendimiento - patrones de rendimiento
- Pruebas de Código Asíncrono - pruebas asíncronas
- Concurrencia - GIL y modelos
- 40 Reglas de Diseño de API - reglas de API asíncrona
Versiones de Stack: Esta página fue escrita para Python 3.14.0, FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.