Los timeouts y la cancelación en asyncio impiden que una operación lenta bloquee una aplicación indefinidamente. La cancelación es cooperativa: la tarea recibe CancelledError en el siguiente punto de espera y puede liberar recursos. El código debe respetar esa señal para que la concurrencia estructurada funcione.

Empieza por la guía de async y await en Python si no conoces las corrutinas. Para tareas hijas relacionadas, consulta asyncio.TaskGroup.

Limita un bloque con asyncio.timeout

Python 3.11 o superior incluye asyncio.timeout() para aplicar un único plazo a varias operaciones:

import asyncio

async def cargar_pagina():
    try:
        async with asyncio.timeout(2.5):
            conexion = await abrir_conexion()
            return await conexion.recibir()
    except TimeoutError:
        return None

Captura TimeoutError fuera del bloque. Internamente, el context manager cancela la tarea y transforma CancelledError al salir. Para un plazo absoluto, calcula con el reloj monotónico del event loop y usa asyncio.timeout_at().

wait_for limita un awaitable

resultado = await asyncio.wait_for(consultar_api(), timeout=2)

Cuando vence el tiempo, wait_for cancela el awaitable y espera a que termine su cancelación. El tiempo real puede superar dos segundos si la limpieza tarda. asyncio.wait, en cambio, devuelve conjuntos de tareas terminadas y pendientes sin cancelar automáticamente las pendientes.

Libera recursos con finally

async def consumir(cola):
    recurso = await abrir_recurso()
    try:
        while True:
            item = await cola.get()
            await procesar(item)
            cola.task_done()
    finally:
        await recurso.cerrar()

Si capturas asyncio.CancelledError para registrar o limpiar, ejecuta raise después. Ocultarlo puede provocar comportamientos inesperados en TaskGroup y asyncio.timeout().

async def worker():
    try:
        await ejecutar()
    except asyncio.CancelledError:
        logger.info("worker cancelado")
        raise

Cancela y espera la tarea

Solicitar cancelación no significa que la tarea ya se haya detenido:

tarea = asyncio.create_task(worker())

try:
    await usar_tarea(tarea)
finally:
    tarea.cancel()
    try:
        await tarea
    except asyncio.CancelledError:
        pass

Esperar permite que los bloques finally internos se ejecuten y evita tareas huérfanas. Los servicios también necesitan un plazo para el apagado y deben registrar las limpiezas que no terminan.

Usa shield con moderación

asyncio.shield() evita que la cancelación del llamador se propague a una tarea interna, pero el llamador sigue recibiendo CancelledError. Puede servir para terminar una escritura crítica ya iniciada. Conserva una referencia fuerte a la tarea y define quién observará su resultado o excepción.

No uses shield para ocultar una arquitectura sin límites. Las operaciones externas necesitan timeout propio y el apagado necesita un presupuesto total.

Prueba los caminos lentos

Prueba finalización normal, timeout, cancelación externa y errores durante la limpieza. Comprueba que conexiones, locks y elementos de cola se liberen. Usa sincronización controlada en las pruebas, no pausas largas que vuelvan inestable la suite.

La documentación oficial de tareas y timeouts de asyncio, consultada el 28 de julio de 2026, explica la conversión a TimeoutError, la propagación y los límites de shield.