Tareas y Gather
Las tareas envuelven corrutinas para su programación concurrente en el bucle de eventos. asyncio.gather espera múltiples awaitables juntos, la base de la E/S paralela en asyncio.
Receta
import asyncio
async def fetch(n: int) -> int:
await asyncio.sleep(0.1)
return n
async def main() -> None:
tasks = [asyncio.create_task(fetch(i)) for i in range(3)]
print(await asyncio.gather(*tasks))
asyncio.run(main())Cuándo usar esto:
- Llamadas concurrentes HTTP/DB con resultados independientes
- Tareas en segundo plano junto con el manejo de solicitudes
- Fan-in antes de responder (se requieren todos los datos)
- Fallo parcial opcional con
return_exceptions=True - Puenteo de APIs de callback a través de
create_task
Ejemplo Funcional
import asyncio
async def download(url_id: int) -> dict:
await asyncio.sleep(0.05)
if url_id == 2:
raise ValueError("respuesta incorrecta")
return {"id": url_id, "bytes": 100}
async def all_or_nothing() -> None:
try:
await asyncio.gather(*(download(i) for i in range(4)))
except ValueError as exc:
print("falló", exc)
async def collect_errors() -> None:
results = await asyncio.gather(
*(download(i) for i in range(4)),
return_exceptions=True,
)
for r in results:
print(type(r).__name__, r)
asyncio.run(all_or_nothing())
asyncio.run(collect_errors())Lo que esto demuestra:
gatherpor defecto falla rápidamente ante la primera excepciónreturn_exceptions=Truedevuelve objetos de excepción en la lista de resultados- Las tareas comienzan a ejecutarse tan pronto como
create_tasklas programa - El orden de los resultados de
gathercoincide con el orden de envío, no con el orden de finalización
Profundización
Tarea vs Corrutina
| Corrutina | Tarea | |
|---|---|---|
| Programación | Esperar para ejecutar | Programada inmediatamente |
| Cancelación | N/A | task.cancel() |
| Inspección | Limitada | .done(), .exception() |
gather vs TaskGroup
gather- fan-in conveniente, compatible con versiones anterioresTaskGroup(3.11+) - concurrencia estructurada, cancela automáticamente a los hermanos en caso de fallo
Errores Comunes
create_tasksin referencia - la tarea es recolectada por el recolector de basura a mitad de ejecución en casos extremos. Solución: mantener la referencia hasta que termine.gathersinreturn_exceptionsen APIs de éxito parcial - un fallo pierde todos los resultados. Solución:return_exceptions=TrueoTaskGroup.- Código bloqueante dentro de tareas - detiene todas las tareas. Solución:
to_thread. - Creación de tareas ilimitada - un millón de tareas agota la memoria. Solución: semáforos, grupos de trabajadores, lotes.
- Disparar y olvidar sin manejador de errores -
task.exception()nunca recuperado genera advertencias de registro. Solución: callback o un wrapper deasyncio.create_taskque registre fallos.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
TaskGroup | Código nuevo, relación estricta padre/hijo | Necesita la ergonomía de return_exceptions de gather |
asyncio.as_completed | Procesar resultados a medida que estén listos | Necesita la lista completa al final |
Semaphore + gather | Limitar la concurrencia | Fan-out pequeño |
Preguntas Frecuentes
¿`create_task` vs `ensure_future`?
create_task para corrutinas en un bucle en ejecución; ensure_future es más amplio (interoperabilidad heredada).
¿`gather` se ejecuta en paralelo?
Concurrente en un solo hilo: esperas intercaladas, no hilos paralelos del sistema operativo.
¿Cómo limitar la concurrencia?
asyncio.Semaphore(n) alrededor del trabajo dentro de cada tarea.
¿Cancelar todo al alcanzar el tiempo límite?
async with asyncio.timeout(5): envolviendo gather o TaskGroup.
¿`gather` vs `wait`?
wait devuelve conjuntos de tareas completadas/pendientes; gather devuelve una lista de valores/excepciones.
¿Puedo usar `gather` con tipos mixtos?
Sí: los awaitables incluyen tareas, corrutinas, futures.
¿Nombres de tareas?
create_task(coro, name="fetch-user") ayuda en los registros de depuración (3.8+).
¿Excepción en disparar y olvidar?
Añadir task.add_done_callback registrando task.exception().
¿`gather` preserva el orden?
Sí: el orden de la lista de resultados coincide con el orden de los awaitables de entrada.
¿`gather` anidado?
Funciona, pero aplánalo cuando sea posible; usa un semáforo en el nivel exterior para mayor claridad.
Relacionado
- Concurrencia Estructurada -
TaskGroup - Fundamentos de Asyncio - introducción a las corrutinas
- HTTP Asíncrono con httpx/aiohttp - solicitudes concurrentes
- Depuración de Código Asíncrono - fugas de tareas
Versiones de la Pila: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), 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+.