Background tasks

Intermedio
FastAPI
FastAPI
Actualizado: 01/07/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Crear tareas en background con BackgroundTasks

Las tareas en background permiten ejecutar operaciones que no requieren que el cliente espere una respuesta inmediata. En lugar de hacer que el usuario aguarde mientras se procesa un archivo o se envía un email, podemos devolver la respuesta HTTP de forma inmediata y ejecutar estas operaciones en segundo plano.

FastAPI proporciona la clase BackgroundTasks que facilita la ejecución de funciones después de que se haya enviado la respuesta al cliente. Esta funcionalidad es especialmente útil para operaciones que pueden tardar varios segundos pero que no afectan directamente al resultado que el usuario necesita recibir.

Importación y uso básico

Para utilizar tareas en background, necesitamos importar BackgroundTasks desde FastAPI y añadirlo como parámetro en nuestras funciones de ruta:

from fastapi import FastAPI, BackgroundTasks

app = FastAPI()

def escribir_log(mensaje: str):
    """Función que se ejecutará en background"""
    with open("actividad.log", "a") as archivo:
        archivo.write(f"{mensaje}\n")

@app.post("/procesar-pedido/")
async def procesar_pedido(pedido_id: int, background_tasks: BackgroundTasks):
    # Procesamiento inmediato del pedido
    resultado = {"pedido_id": pedido_id, "estado": "procesado"}
    
    # Añadir tarea en background
    background_tasks.add_task(escribir_log, f"Pedido {pedido_id} procesado correctamente")
    
    return resultado

En este ejemplo, la función escribir_log se ejecutará después de que se envíe la respuesta al cliente. El usuario recibirá inmediatamente la confirmación del pedido sin esperar a que se escriba el archivo de log.

Añadir múltiples tareas

Podemos añadir varias tareas en background dentro de la misma función de ruta. Cada tarea se ejecutará de forma secuencial en el orden que fueron añadidas:

def enviar_email_confirmacion(email: str, pedido_id: int):
    """Simula el envío de un email de confirmación"""
    print(f"Enviando email a {email} para pedido {pedido_id}")
    # Aquí iría la lógica real de envío de email

def actualizar_estadisticas(pedido_id: int):
    """Actualiza las estadísticas de ventas"""
    print(f"Actualizando estadísticas para pedido {pedido_id}")
    # Aquí iría la lógica de actualización

@app.post("/crear-pedido/")
async def crear_pedido(email: str, background_tasks: BackgroundTasks):
    pedido_id = 12345  # Simulamos la creación del pedido
    
    # Añadir múltiples tareas en background
    background_tasks.add_task(enviar_email_confirmacion, email, pedido_id)
    background_tasks.add_task(actualizar_estadisticas, pedido_id)
    background_tasks.add_task(escribir_log, f"Nuevo pedido creado: {pedido_id}")
    
    return {"pedido_id": pedido_id, "mensaje": "Pedido creado exitosamente"}

Pasar argumentos a las tareas

Las tareas en background pueden recibir tanto argumentos posicionales como argumentos con nombre. Esto nos permite pasar toda la información necesaria a nuestras funciones:

def procesar_archivo(nombre_archivo: str, formato: str = "pdf", comprimir: bool = False):
    """Procesa un archivo con opciones específicas"""
    print(f"Procesando {nombre_archivo} en formato {formato}")
    if comprimir:
        print("Aplicando compresión al archivo")
    # Lógica de procesamiento del archivo

@app.post("/subir-archivo/")
async def subir_archivo(nombre: str, background_tasks: BackgroundTasks):
    # Simulamos la subida del archivo
    archivo_guardado = f"uploads/{nombre}"
    
    # Añadir tarea con argumentos posicionales y con nombre
    background_tasks.add_task(
        procesar_archivo, 
        archivo_guardado,           # Argumento posicional
        formato="png",              # Argumento con nombre
        comprimir=True              # Argumento con nombre
    )
    
    return {"archivo": nombre, "estado": "subido"}

Manejo de errores en tareas background

Es importante considerar que las tareas en background se ejecutan después de enviar la respuesta, por lo que los errores que ocurran en estas tareas no afectarán la respuesta HTTP. Sin embargo, debemos manejarlos adecuadamente:

def tarea_con_manejo_errores(usuario_id: int):
    """Tarea que maneja sus propios errores"""
    try:
        # Simulamos una operación que puede fallar
        if usuario_id < 0:
            raise ValueError("ID de usuario inválido")
        
        print(f"Procesando usuario {usuario_id}")
        # Lógica de procesamiento
        
    except Exception as e:
        # Registrar el error para debugging
        with open("errores.log", "a") as archivo:
            archivo.write(f"Error en tarea background: {str(e)}\n")

@app.post("/procesar-usuario/{usuario_id}")
async def procesar_usuario(usuario_id: int, background_tasks: BackgroundTasks):
    background_tasks.add_task(tarea_con_manejo_errores, usuario_id)
    
    return {"usuario_id": usuario_id, "estado": "en_proceso"}

Funciones asíncronas como tareas background

También podemos utilizar funciones asíncronas como tareas en background. Esto es útil cuando necesitamos realizar operaciones asíncronas como llamadas a APIs externas:

import httpx

async def notificar_servicio_externo(pedido_id: int):
    """Notifica a un servicio externo sobre el nuevo pedido"""
    async with httpx.AsyncClient() as client:
        try:
            response = await client.post(
                "https://api.externa.com/notificaciones",
                json={"pedido_id": pedido_id, "evento": "pedido_creado"}
            )
            print(f"Notificación enviada: {response.status_code}")
        except Exception as e:
            print(f"Error al notificar: {e}")

@app.post("/pedido-con-notificacion/")
async def crear_pedido_con_notificacion(pedido_id: int, background_tasks: BackgroundTasks):
    # Añadir función asíncrona como tarea background
    background_tasks.add_task(notificar_servicio_externo, pedido_id)
    
    return {"pedido_id": pedido_id, "notificacion": "programada"}

Las tareas en background de FastAPI proporcionan una solución sencilla y eficaz para operaciones que no requieren respuesta inmediata, mejorando significativamente la experiencia del usuario al reducir los tiempos de respuesta de la API.

¿Te está gustando esta lección?

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

Casos de uso típicos (emails, logs)

Las tareas en background son especialmente útiles en escenarios donde necesitamos realizar operaciones que pueden tardar tiempo pero que no afectan la respuesta inmediata al usuario. Los casos más comunes incluyen el envío de emails de confirmación, registro de actividades y procesamiento de archivos.

Envío de emails de confirmación

Uno de los casos más frecuentes es el envío de emails después de que un usuario realiza una acción importante como registrarse, realizar una compra o cambiar su contraseña. En lugar de hacer esperar al usuario mientras se envía el email, podemos confirmar la acción inmediatamente y enviar el email en background:

from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel, EmailStr
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

app = FastAPI()

class UsuarioRegistro(BaseModel):
    nombre: str
    email: EmailStr
    telefono: str

def enviar_email_bienvenida(email: str, nombre: str):
    """Envía email de bienvenida al nuevo usuario"""
    try:
        # Configuración del servidor SMTP (ejemplo con Gmail)
        servidor = smtplib.SMTP('smtp.gmail.com', 587)
        servidor.starttls()
        servidor.login("tu_email@gmail.com", "tu_password")
        
        # Crear el mensaje
        mensaje = MIMEMultipart()
        mensaje['From'] = "tu_email@gmail.com"
        mensaje['To'] = email
        mensaje['Subject'] = "¡Bienvenido a nuestra plataforma!"
        
        cuerpo = f"""
        Hola {nombre},
        
        ¡Gracias por registrarte en nuestra plataforma!
        Tu cuenta ha sido creada exitosamente.
        
        Saludos,
        El equipo de desarrollo
        """
        
        mensaje.attach(MIMEText(cuerpo, 'plain'))
        servidor.send_message(mensaje)
        servidor.quit()
        
        print(f"Email de bienvenida enviado a {email}")
        
    except Exception as e:
        print(f"Error enviando email: {e}")

@app.post("/registrar-usuario/")
async def registrar_usuario(usuario: UsuarioRegistro, background_tasks: BackgroundTasks):
    # Simular guardado en base de datos
    usuario_id = 12345
    
    # Programar envío de email en background
    background_tasks.add_task(enviar_email_bienvenida, usuario.email, usuario.nombre)
    
    return {
        "mensaje": "Usuario registrado exitosamente",
        "usuario_id": usuario_id,
        "email_confirmacion": "programado"
    }

Sistema de logging y auditoría

El registro de actividades es fundamental para el monitoreo y debugging de aplicaciones. Las tareas en background nos permiten registrar eventos sin impactar el rendimiento de las operaciones principales:

from datetime import datetime
import json

def registrar_actividad(usuario_id: int, accion: str, detalles: dict = None):
    """Registra actividad del usuario en archivo de log"""
    timestamp = datetime.now().isoformat()
    
    entrada_log = {
        "timestamp": timestamp,
        "usuario_id": usuario_id,
        "accion": accion,
        "detalles": detalles or {}
    }
    
    try:
        with open("actividad_usuarios.log", "a", encoding="utf-8") as archivo:
            archivo.write(json.dumps(entrada_log, ensure_ascii=False) + "\n")
        
        print(f"Actividad registrada: {accion} para usuario {usuario_id}")
        
    except Exception as e:
        print(f"Error registrando actividad: {e}")

def registrar_error(endpoint: str, error: str, usuario_id: int = None):
    """Registra errores de la aplicación"""
    timestamp = datetime.now().isoformat()
    
    entrada_error = {
        "timestamp": timestamp,
        "tipo": "error",
        "endpoint": endpoint,
        "error": error,
        "usuario_id": usuario_id
    }
    
    try:
        with open("errores_aplicacion.log", "a", encoding="utf-8") as archivo:
            archivo.write(json.dumps(entrada_error, ensure_ascii=False) + "\n")
            
    except Exception as e:
        print(f"Error registrando error: {e}")

@app.post("/actualizar-perfil/{usuario_id}")
async def actualizar_perfil(usuario_id: int, datos: dict, background_tasks: BackgroundTasks):
    try:
        # Simular actualización del perfil
        print(f"Actualizando perfil del usuario {usuario_id}")
        
        # Registrar la actividad en background
        background_tasks.add_task(
            registrar_actividad,
            usuario_id,
            "perfil_actualizado",
            {"campos_modificados": list(datos.keys())}
        )
        
        return {"mensaje": "Perfil actualizado correctamente"}
        
    except Exception as e:
        # Registrar el error en background
        background_tasks.add_task(
            registrar_error,
            f"/actualizar-perfil/{usuario_id}",
            str(e),
            usuario_id
        )
        
        return {"error": "Error actualizando perfil"}, 500

Procesamiento de archivos y reportes

Otro caso común es el procesamiento de archivos subidos por usuarios, como imágenes que necesitan redimensionarse o documentos que requieren análisis:

from PIL import Image
import os

def procesar_imagen(ruta_archivo: str, usuario_id: int):
    """Procesa imagen subida: redimensiona y crea thumbnails"""
    try:
        # Abrir la imagen original
        with Image.open(ruta_archivo) as img:
            # Crear versión redimensionada (máximo 1920x1080)
            img.thumbnail((1920, 1080), Image.Resampling.LANCZOS)
            
            # Guardar versión optimizada
            nombre_base = os.path.splitext(ruta_archivo)[0]
            ruta_optimizada = f"{nombre_base}_optimizada.jpg"
            img.save(ruta_optimizada, "JPEG", quality=85, optimize=True)
            
            # Crear thumbnail (200x200)
            img_thumb = img.copy()
            img_thumb.thumbnail((200, 200), Image.Resampling.LANCZOS)
            ruta_thumbnail = f"{nombre_base}_thumb.jpg"
            img_thumb.save(ruta_thumbnail, "JPEG", quality=80)
            
        print(f"Imagen procesada para usuario {usuario_id}: {ruta_archivo}")
        
        # Registrar el procesamiento
        registrar_actividad(
            usuario_id,
            "imagen_procesada",
            {
                "archivo_original": ruta_archivo,
                "archivo_optimizado": ruta_optimizada,
                "thumbnail": ruta_thumbnail
            }
        )
        
    except Exception as e:
        print(f"Error procesando imagen: {e}")
        registrar_error("procesamiento_imagen", str(e), usuario_id)

def generar_reporte_mensual(usuario_id: int, mes: int, año: int):
    """Genera reporte mensual de actividad del usuario"""
    try:
        # Simular generación de reporte
        print(f"Generando reporte mensual para usuario {usuario_id}: {mes}/{año}")
        
        # Aquí iría la lógica real de generación del reporte
        # Por ejemplo: consultar base de datos, procesar datos, crear PDF
        
        nombre_reporte = f"reporte_{usuario_id}_{año}_{mes:02d}.pdf"
        
        # Simular creación del archivo
        with open(f"reportes/{nombre_reporte}", "w") as archivo:
            archivo.write(f"Reporte mensual para usuario {usuario_id}\n")
            archivo.write(f"Período: {mes}/{año}\n")
            archivo.write("Datos del reporte...\n")
        
        print(f"Reporte generado: {nombre_reporte}")
        
        # Registrar la generación del reporte
        registrar_actividad(
            usuario_id,
            "reporte_generado",
            {"archivo": nombre_reporte, "periodo": f"{mes}/{año}"}
        )
        
    except Exception as e:
        print(f"Error generando reporte: {e}")
        registrar_error("generacion_reporte", str(e), usuario_id)

@app.post("/subir-imagen/{usuario_id}")
async def subir_imagen(usuario_id: int, background_tasks: BackgroundTasks):
    # Simular guardado de archivo
    ruta_archivo = f"uploads/imagen_{usuario_id}_123.jpg"
    
    # Procesar imagen en background
    background_tasks.add_task(procesar_imagen, ruta_archivo, usuario_id)
    
    return {
        "mensaje": "Imagen subida correctamente",
        "procesamiento": "en_curso"
    }

@app.post("/solicitar-reporte/{usuario_id}")
async def solicitar_reporte(usuario_id: int, mes: int, año: int, background_tasks: BackgroundTasks):
    # Generar reporte en background
    background_tasks.add_task(generar_reporte_mensual, usuario_id, mes, año)
    
    return {
        "mensaje": "Reporte solicitado",
        "estado": "generando",
        "periodo": f"{mes}/{año}"
    }

Combinando múltiples tareas

En aplicaciones reales, es común combinar varios tipos de tareas en background para una sola operación:

@app.post("/completar-compra/")
async def completar_compra(
    usuario_id: int,
    email: str,
    productos: list,
    background_tasks: BackgroundTasks
):
    # Simular procesamiento de la compra
    compra_id = 98765
    total = sum(producto.get("precio", 0) for producto in productos)
    
    # Programar múltiples tareas en background
    background_tasks.add_task(
        enviar_email_bienvenida,  # Reutilizamos la función de email
        email,
        "Cliente"
    )
    
    background_tasks.add_task(
        registrar_actividad,
        usuario_id,
        "compra_completada",
        {
            "compra_id": compra_id,
            "total": total,
            "productos": len(productos)
        }
    )
    
    background_tasks.add_task(
        generar_reporte_mensual,
        usuario_id,
        datetime.now().month,
        datetime.now().year
    )
    
    return {
        "compra_id": compra_id,
        "total": total,
        "mensaje": "Compra procesada exitosamente",
        "confirmacion_email": "enviando"
    }

Estos casos de uso demuestran cómo las tareas en background mejoran significativamente la experiencia del usuario al permitir que las operaciones principales respondan rápidamente, mientras que las tareas secundarias se ejecutan de forma transparente en segundo plano.

Aprendizajes de esta lección

  • Comprender qué son las tareas en background y su utilidad en FastAPI.
  • Aprender a usar la clase BackgroundTasks para ejecutar funciones después de responder al cliente.
  • Saber cómo añadir múltiples tareas y pasar argumentos a estas funciones.
  • Conocer buenas prácticas para el manejo de errores en tareas en background.
  • Explorar casos de uso típicos como envío de emails, logging y procesamiento de archivos.

Completa FastAPI y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración