asyncio.eager_task_factory permite que una corrutina comience a ejecutarse inmediatamente cuando se crea una tarea, antes de pasar por la planificación normal del event loop. Esto puede reducir overhead en operaciones muy cortas, especialmente accesos a caché y validaciones en memoria, pero también modifica el orden de ejecución y exige cuidado con efectos secundarios, excepciones, cancelación y pruebas.
Qué significa ejecución eager
En el modelo tradicional de asyncio, llamar a asyncio.create_task() programa la corrutina para una próxima vuelta del event loop. Con una task factory eager, la corrutina empieza de forma síncrona durante la creación de la tarea. Si termina sin bloquearse, puede completarse sin entrar en la cola normal del planificador.
Este comportamiento puede beneficiar aplicaciones que crean muchas corrutinas pequeñas. Ejemplos comunes son búsquedas en caché, validaciones, cálculos memoizados y wrappers que con frecuencia devuelven un resultado inmediato. Aun así, la optimización debe medirse con cargas reales.
Cómo activar la factory
import asyncio
async def leer_cache(clave, cache):
if clave in cache:
return cache[clave]
await asyncio.sleep(0.01)
return None
async def main():
loop = asyncio.get_running_loop()
loop.set_task_factory(asyncio.eager_task_factory)
cache = {"curso": "Python"}
tarea = asyncio.create_task(leer_cache("curso", cache))
resultado = await tarea
print(resultado)
asyncio.run(main())La configuración afecta al loop actual. En aplicaciones grandes conviene definirla en un único punto de inicialización y documentarla para evitar diferencias silenciosas entre componentes.
Cuándo vuelve a la planificación normal
Si la corrutina llega a un await que no puede completarse inmediatamente, la tarea pasa al event loop de manera normal. Por tanto, la ejecución eager beneficia sobre todo la parte síncrona inicial y las corrutinas que terminan sin suspensión.
Esto resulta útil en funciones que consultan primero una caché y solo después realizan I/O. Un acierto puede finalizar inmediatamente, mientras que un fallo de caché continúa por el flujo asíncrono habitual.
Cambios en el orden de ejecución
La diferencia semántica más importante es el orden. Código que antes creaba una tarea y ejecutaba otra instrucción antes de que la corrutina comenzara puede dejar de comportarse así.
import asyncio
async def trabajo():
print("la corrutina comenzó")
return 42
async def main():
loop = asyncio.get_running_loop()
loop.set_task_factory(asyncio.eager_task_factory)
print("antes")
tarea = asyncio.create_task(trabajo())
print("después")
print(await tarea)
asyncio.run(main())Con ejecución eager, el mensaje de la corrutina puede aparecer antes de después. Esto puede revelar dependencias ocultas de orden en logs, métricas, mutaciones de estado, callbacks y preparación de recursos.
Excepciones más tempranas
Una corrutina que falla antes de su primer await bloqueante puede producir el error durante la ejecución inicial. Las pruebas deben cubrir tanto caminos que terminan inmediatamente como caminos que se suspenden.
async def validar(valor):
if valor < 0:
raise ValueError("valor inválido")
return valorEn sistemas con grupos de tareas o cierre estructurado, revisa cómo estos errores interactúan con cancelación y limpieza. Consulta asyncio.Runner para comprender el ciclo de vida del loop y asyncio.Barrier para sincronización por fases.
Caso de uso con caché
El escenario clásico es una función asíncrona que comprueba estado local antes de realizar una petición externa.
async def obtener_usuario(user_id, cache, cliente):
if user_id in cache:
return cache[user_id]
respuesta = await cliente.get(f"/users/{user_id}")
cache[user_id] = respuesta
return respuestaSi la tasa de aciertos es alta, la ejecución eager puede evitar costes repetidos de planificación. Si casi todas las llamadas llegan a la red, la mejora puede ser mínima.
Riesgos con estado compartido
Como la corrutina puede ejecutarse inmediatamente, cambios en listas, diccionarios, contadores o variables de contexto pueden ocurrir antes de lo esperado. Es preferible usar transiciones explícitas, valores inmutables cuando sea viable y mecanismos de sincronización probados.
Para comunicación FIFO segura entre hilos, revisa queue.SimpleQueue. Para paralelismo de tareas CPU-bound, consulta InterpreterPoolExecutor.
Benchmark correcto
Un benchmark útil debe separar finalización inmediata, suspensión breve y tareas dominadas por I/O. En cargas de red puede no existir diferencia visible, mientras que millones de aciertos de caché sí pueden mostrar una mejora.
import asyncio
import time
async def inmediato():
return 1
async def medir(cantidad):
inicio = time.perf_counter()
tareas = [asyncio.create_task(inmediato()) for _ in range(cantidad)]
await asyncio.gather(*tareas)
return time.perf_counter() - inicioRepite las mediciones, descarta calentamiento, compara versiones de Python y observa CPU, memoria, throughput y latencia de cola.
Estrategia de pruebas
Prueba orden de logs, callbacks, excepciones antes del primer await, aciertos y fallos de caché, cancelación, timeouts y limpieza. No verifiques solo el resultado final cuando la secuencia sea parte del contrato.
Cancelación
Una tarea que termina de forma eager puede finalizar antes de que el código de cancelación actúe. Una tarea que se suspende sigue las reglas normales. Por eso es importante probar caminos rápidos y lentos.
Observabilidad
Las métricas y trazas pueden observar eventos en otro orden. Crea spans y contexto antes de construir la tarea cuando deban incluir el trabajo eager. Las herramientas de bajo nivel no deberían asumir que toda tarea aparece primero en la cola regular.
Despliegue en producción
Introduce la factory detrás de una opción de configuración, compara latencias y errores, documenta el cambio semántico y despliega gradualmente cuando existan callbacks complejos o estado compartido.
Buenas prácticas
Activa la factory de manera centralizada, evita dependencias accidentales de orden, mide cargas reales, conserva garantías de limpieza y mantén pruebas de regresión. Debe tratarse como una optimización semántica, no como un simple interruptor de rendimiento.
La documentación oficial de tareas asyncio explica la API y sus advertencias. La documentación general de asyncio describe el event loop y sus primitivas.
Conclusión
asyncio.eager_task_factory puede reducir el coste de corrutinas cortas y flujos con caché, pero cambia el momento exacto en que el código comienza. Úsala con beneficios medidos, pruebas sólidas y una arquitectura preparada para ejecución anticipada.







