Python ejecutar funciones de forma asíncrona y en background

3 minutos

Python

Disponemos de diversas librerías y sintaxis nativa en Python para ejecutar instrucciones de fondo, permitiendo continuar las acciones mientras por detrás se completan. De este modo evitaremos bloqueos del hilo principal o esperas innecesarias mientras esperamos una respuesta (un API, disco duro, calculo complejo…). Y entre todas las posibilidades voy a dejar un par de ejemplos de como usar threading.

¿Por qué deberíamos acostumbrarnos a usarla? Es un error clásico en el desarrollo web hacer esperar a un usuario antes de darle una respuesta. Pongamos de ejemplo un formulario de contacto: hasta que el email no se ha enviado no le decimos que todo ha ido bien, haciendo que espere sin sentido mientras conectamos con el servidor SMTP, generamos el cuerpo del correo, tal vez algún PDF, lo enviamos, almacenamos una copia en la base de datos, puede que algunas copias a los administradores, etc… ¿Por qué no dejar esa tarea en un hilo asíncrono en background? El visitante percibirá una respuesta casi instantánea de que todo ha ido bien, ganando fluidez cuando interactúe con la aplicación.

Threading

Es la manera más sencilla.

En el siguiente ejemplo lanzamos function_1 en background.

import threading

# Función que se ejecutará de forma asíncrona

def funcion_1(parametro_1):
    pass

# Declaro un hilo de ejecución. "args" debe ser una Tupla.

threading_emails = threading.Thread(target=funcion_1, args=(parametro_1,))

# Lo lanzo

threading_emails.start()

# Resto de mi código que se ejecutará de forma paralela
...


Veamos un ejemplo con código real.

import threading
import time

# Función que se ejecutará de forma asíncrona

def saluda(nombre):
    # Espera 2 segundos
    time.sleep(2)
    print(f"Hola {nombre}")

# Declaro un hilo de ejecución

threading_emails = threading.Thread(target=saluda, args=("Snake",))

# Lo lanzo

threading_emails.start()

# Resto de mi código que se ejecutará de forma paralela

print("Soy el resto del código")

Se imprimirá:

Soy el resto del código
Hola Snake

En el caso de necesitar de ejecutar muchas funciones asíncronas, podemos agruparlas en una sola.

import threading

# Funciones que quiero ejecutar de forma asíncrona

def funcion_1(parametro_1):
    pass

def funcion_2():
    pass

def funcion_3(parametro_2):
    pass

def funcion_4(parametro_1, parametro_2):
    pass

# Función que engloba a todas

def conjunto_de_funciones(parametro_1, parametro_2):
    funcion_1(parametro_1)
    funcion_2()
    funcion_3(parametro_2)
    funcion_4(parametro_1, parametro_2)

# Declaro un hilo de ejecución

threading_emails = threading.Thread(target=conjunto_de_funciones, args=(parametro_1, parametro_2))

# Lo lanzo

threading_emails.start()

# Resto de mi código que se ejecutará de forma paralela
...

Lamentablemente posee una pega: No podemos capturar el return, o el valor, de la función. Para solucionarlo podemos utilizar una alternativa llamada futures.

Futures

En el siguiente ejemplo dispongo de una función que deseo ejecutar de forma en paralelo y además deseo capturar sus valores: mi_funcion_de_tiempo. Su tarea es básica, suma el primer argumento con el segundo y espera el tiempo en segundos del tercer argumento.

import time
import concurrent.futures


def mi_funcion_de_tiempo(num1, num2, time_sleep):
    time.sleep(time_sleep)
    return num1 + num2

with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
    mis_functiones_en_hilos = {
        executor.submit(mi_funcion_de_tiempo, 2, 4, 5):
        "La suma de 2 + 4 en 5 segundos",
        executor.submit(mi_funcion_de_tiempo, 3, 5, 3):
        "La suma de 3 + 5 en 3 segundos",
    }
    for future in concurrent.futures.as_completed(mis_functiones_en_hilos):
        etiqueta = mis_functiones_en_hilos[future]
        try:
            if etiqueta == "La suma de 2 + 4 en 5 segundos":
                print("2 + 4 = " + str(future.result()))
            elif etiqueta == "La suma de 3 + 5 en 3 segundos":
                print("3 + 5 = " + str(future.result()))
        except Exception as exc:
            print('%r generated an exception: %s' % (final, exc))
print("Terminadas")

# 3 + 5 = 8
# 2 + 4 = 6
# Terminadas

¡Atención! El ejemplo no es asíncrono. Si no terminan las funciones de ejecutarse, la lógica no continuará. En caso de necesitas paralelizar tu código asíncronamente, puedes consultar la librería asyncio o directamente aprender a usar funciones async.

Te deseo mucho ánimo en diferentes hilos paralelos.

Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional.

Atribución/Reconocimiento-NoComercial-SinDerivados 4.0 Internacional

¿Me ayudas?

No te sientas obligado a realizarme una donación, pero cada aportación me ayuda a mantener el sitio en activo para que continúe existiendo y me motiva a continuar creando nuevo contenido.

Comprame un café
Pulsa sobre la imagen
  • 1 café: Se mantiene el dominio durante 4 meses.
  • 2 cafés: Se liquida 1 mes del Servidor Web.
  • 3 cafés: Se paga 1 mes de Newsletter.

Comentarios

{{ comments.length }} comentarios

Nuevo comentario

Nueva replica  {{ formatEllipsisAuthor(replyComment.author) }}

Acepto la política de Protección de Datos.

Escribe el primer comentario

Tal vez también te interese...