Tema representativo de entrevista

Entrevista técnica: ¿Cómo gestionar fallos concurrentes con asyncio.TaskGroup de Python?

CodingDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Utilice asyncio.TaskGroup de Python para cargar datos de usuario, pedidos y recomendaciones de forma concurrente. Cancele las tareas hermanas cuando una falle, conserve los errores diagnosticables y limpie los recursos tras la cancelación del invocador o un tiempo de espera. Explique la diferencia con respecto a asyncio.gather y qué trabajo no debería residir en el alcance de la solicitud.

Prompt y contexto

Una API asíncrona carga datos de usuario, pedidos y recomendaciones de forma concurrente. Un fallo en una tarea requerida debe cancelar a las tareas hermanas, mientras que una desconexión del cliente o un tiempo de espera total no deben dejar corrutinas huérfanas. Diseñe la implementación con asyncio.TaskGroup y explique la agregación de excepciones, la cancelación y la limpieza de recursos.

La documentación de Python describe TaskGroup como concurrencia estructurada: las tareas finalizan antes de que el alcance termine; una excepción que no sea de cancelación cancela las tareas restantes y se lanza como un ExceptionGroup. La entrevista evalúa el razonamiento sobre el ciclo de vida en lugar de una acumulación de llamadas a create_task().

Qué evalúa el entrevistador

Se busca evaluar el árbol de tareas padre-hijo, la cancelación de tareas hermanas, CancelledError, ExceptionGroup y la unión mediante async with. El candidato debe propagar la cancelación hacia bases de datos, HTTP y archivos, y trasladar el trabajo que debe sobrevivir a la respuesta hacia una cola duradera.

Preguntas para clarificar

  • ¿Son obligatorias las tres lecturas o pueden degradarse las recomendaciones?
  • ¿Qué tareas abren conexiones, cursores o archivos temporales?
  • ¿Quién crea el tiempo de espera total y cómo llega la cancelación del invocador?
  • ¿Qué errores necesitan clasificación, reintentos o una respuesta segura para el usuario?
  • ¿Qué trabajos deben continuar después de la respuesta HTTP?

Respuesta en 30 segundos

“Cree tres tareas dentro de async with TaskGroup() y espere a que finalice el alcance. Una excepción que no sea de cancelación cancela a las hermanas y lanza un grupo de excepciones; except* clasifica los errores conocidos. Cada tarea cierra recursos en finally. Envuelva el grupo en un asyncio.timeout() o en la cancelación del invocador. El trabajo que deba sobrevivir a la solicitud se envía a una cola duradera.”

Solución paso a paso

Paso 1: Definir el árbol de tareas y el contrato de resultados

Declare cada resultado y su nivel de criticidad. El usuario y los pedidos pueden ser obligatorios mientras que las recomendaciones son opcionales; esa elección determina si una excepción cancela el grupo. Cada tarea pertenece al alcance de la solicitud.

python
async with asyncio.TaskGroup() as group:
    user_task = group.create_task(load_user(user_id))
    order_task = group.create_task(load_orders(user_id))
    rec_task = group.create_task(load_recommendations(user_id))

Salir del alcance une las tareas. Leer una tarea no finalizada fuera del bloque no sustituye su unión formal.

Paso 2: Manejar fallos con ExceptionGroup

Una excepción distinta de CancelledError cancela a las hermanas, y TaskGroup lanza un ExceptionGroup después de que todas las tareas finalizan. Utilice except* para errores de dependencias esperados y preserve los errores desconocidos para el manejador externo.

python
try:
    async with asyncio.TaskGroup() as group:
        user = group.create_task(load_user(user_id))
        orders = group.create_task(load_orders(user_id))
except* RetryableDependencyError as errors:
    record_dependency_failures(errors.exceptions)
    raise ServiceUnavailable from errors

No capture BaseException suprimiendo silenciosamente la cancelación.

Paso 3: Propagar la cancelación a la E/S real

TaskGroup cancela tareas de Python; los controladores de HTTP, bases de datos y archivos necesitan soporte para cancelación o tiempo de espera. Cada tarea cierra conexiones, libera semáforos, elimina archivos temporales y detiene el consumo en finally.

python
async def load_orders(user_id: str):
    conn = await pool.acquire()
    try:
        return await conn.fetch("SELECT ...", user_id, timeout=1.5)
    finally:
        await pool.release(conn)

Si un controlador no puede interrumpir una consulta, use un tiempo de espera de sentencia, una conexión aislada o un trabajo en segundo plano acotado en lugar de depender únicamente de la cancelación de la tarea.

Paso 4: Establecer un tiempo de espera total y distinguir la cancelación externa

Envuelva todo el grupo en asyncio.timeout() para que todo el trabajo comparta un único presupuesto de tiempo.

python
try:
    async with asyncio.timeout(2.0):
        result = await aggregate(user_id)
except TimeoutError:
    return degraded_response("deadline")
except asyncio.CancelledError:
    raise

La cancelación externa debe continuar hacia arriba, no convertirse en una respuesta exitosa. Registre el tiempo de espera y la cancelación del usuario como causas diferentes.

Paso 5: Comprender TaskGroup frente a gather

asyncio.gather() normalmente propaga la primera excepción a quien lo espera, pero las tareas hermanas no necesariamente se cancelan; retornar inmediatamente puede dejarlas huérfanas. TaskGroup vincula el ciclo de vida de la tarea a un alcance y cancela a las hermanas ante un fallo.

gather(return_exceptions=True) es útil para un contrato explícito de fallo parcial, pero cada resultado debe inspeccionarse. No es un sustituto de la limpieza estructurada, y un grupo no debe crear tareas en segundo plano sin límites.

Paso 6: Probar el orden de fallos y la limpieza

Inyecte fallo primero en usuario, fallo primero en recomendaciones, fallos simultáneos, cancelación del invocador, tiempo de espera total, tiempo de espera del controlador y un finally que falle. Valide que las hermanas reciban la cancelación, los recursos se liberen, no queden tareas pendientes y cada excepción pueda vincularse a una tarea hija.

Registre el nombre de la tarea, ID de solicitud, duración, causa de la cancelación, tipo de excepción y llamada descendente sin incluir cargas útiles privadas. Repita pruebas de E/S lenta para exponer condiciones de carrera; las pruebas donde todo es exitoso son insuficientes.

Respuesta modelo

“Creo tres tareas dentro de TaskGroup, hago que usuario y pedidos sean obligatorios, y permito que las recomendaciones se degraden. El alcance une las tareas; un fallo que no sea de cancelación cancela a las hermanas y lanza un ExceptionGroup, que except* clasifica. Cada tarea de E/S pasa un tiempo de espera y libera conexiones en finally.”

“Un tiempo de espera externo proporciona un presupuesto compartido y CancelledError se propaga. El trabajo posterior a la respuesta se envía a una cola duradera. Inyecto fallos simultáneos, cancelación, tiempo de espera y E/S lenta, y verifico que no haya tareas huérfanas ni fugas de recursos.”

Errores comunes

  • Retornar tras un simple create_task tareas huérfanas → mantener las tareas dentro del alcance de un TaskGroup.
  • Suprimir CancelledError el padre no puede detenerse → limpiar y volver a lanzar.
  • Dar a cada tarea un tiempo de espera completo → la latencia total se descontrola → compartir un solo presupuesto.
  • Tratar ExceptionGroup como un solo error → se pierde la evidencia paralela → clasificar con except*.
  • Cancelar solo tareas de Python → el trabajo en base de datos o HTTP continúa → usar cancelación del controlador o tiempos de espera.
  • Usar gather para todos los casos → el fallo parcial y la limpieza se vuelven ambiguos → definir el contrato de degradación.

Preguntas de seguimiento y respuestas

Pregunta de seguimiento 1: ¿TaskGroup detiene instantáneamente la E/S subyacente?

No. El controlador debe admitir la cancelación o un tiempo de espera de sentencia; de lo contrario, aísle la conexión o traslade el trabajo a un proceso en segundo plano acotado.

Pregunta de seguimiento 2: ¿Por qué no cancelar el grupo cuando fallan las recomendaciones?

Utilice el contrato de negocio. Una recomendación opcional se puede capturar y sustituir por un resultado vacío; el trabajo crítico para la seguridad o la facturación debe permitir que la excepción escape y cancele a las hermanas.

Pregunta de seguimiento 3: ¿Cómo se asigna un ExceptionGroup a una respuesta de API?

Mapee los errores de dependencias conocidos a un 503 seguro o a un resultado degradado y registre los errores hijos. Los errores desconocidos van al manejador global; nunca devuelva trazas de pila internas al cliente.

Pregunta de seguimiento 4: ¿Qué trabajo pertenece fuera de TaskGroup?

El envío de correos electrónicos, la indexación y las escrituras por lotes que deben continuar tras la respuesta deben persistirse en una cola y ser reintentados por un worker. El alcance de la solicitud solo debe contener trabajo con ciclo de vida de solicitud.

Pregunta de seguimiento 5: ¿Qué sucede si finally lanza una excepción durante la cancelación?

Mantenga la limpieza pequeña y observable, adjunte los fallos de limpieza como contexto y preserve la cancelación original o la excepción del negocio para que no se reemplace la causa raíz.

Fuentes públicas

Preguntas relacionadas

Herramienta de entrevista relacionada

Usa Captura para un ejercicio de código

Captura el problema y luego aborda en orden las restricciones, la solución, el código, los casos extremos y la complejidad.

Ver la herramienta