
Disponemos de diversas bibliotecas 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…). En mi caso es muy útil para enviar correos electrónicos, ya que no es necesario esperar a que se envíen todos los correos para continuar con el resto del código. O también cuando dependo de una API externa que tarda en responder. Estoy seguro que tú también tienes casos similares.
Y entre todas las posibilidades que puedes encontrar, te voy a dejar un par de ejemplos de como lograrlo usando threading o futures.
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
La biblioteca concurrent.futures
es un poco más compleja pero tiene algunas ventajas respecto a threading
. Permite capturar el resultado de las funciones e identificar el hilo que la ejecuta.
La estructura sería tal que así:
import concurrent.futures
# Función que se ejecutará de forma asíncrona
def funcion_a_ejecutar(argumento_1, argumento_2):
pass
# Declaro un hilo de ejecución. Con `max_workers` le indico el número de hilos
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
# Envío la función a ejecutar y sus argumentos
future = executor.submit(funcion_a_ejecutar, argumento_1, argumento_2)
# Aquí puedes hacer otras cosas mientras la función se ejecuta
# ...
# Obtener el resultado
resultado = future.result()
En el caso que desees ejecutar varias funciones en paralelo, puedes hacerlo de la siguiente manera:
import concurrent.futures
# Función que se ejecutará de forma asíncrona
def funcion_a_ejecutar(argumento_1, argumento_2):
pass
# Declaro un hilo de ejecución. Con `max_workers` le indico el número de hilos
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
# Envío la función a ejecutar y sus argumentos
mis_funciones = {
executor.submit(funcion_a_ejecutar, argumento_1, argumento_2):
"Etiqueta de la función 1",
executor.submit(funcion_a_ejecutar, argumento_3, argumento_4):
"Etiqueta de la función 2",
}
# Aquí puedes hacer otras cosas mientras las funciones se ejecutan
# ...
# Obtener los resultados
for future in concurrent.futures.as_completed(mis_funciones):
etiqueta = mis_funciones[future]
try:
if etiqueta == "Etiqueta de la función 1":
print("Resultado de la función 1: " + str(future.result()))
elif etiqueta == "Etiqueta de la función 2":
print("Resultado de la función 2: " + str(future.result()))
except Exception as exc:
print('%r generated an exception: %s' % (future, exc))
Las etiquetas deben ser únicas. Hay mil trucos para hacer que sean únicas, como añadir un id
si estas usando datos de una base de datos, un uuid
, una secuencia de números, enumerate
, etc.
Vamos a ver un ejemplo sencillo donde sumo 4 veces 2 números y los imprimo en pantalla. En lugar de hacerlo de forma secuencial (uno detrás de otro), lo haré de forma asíncrona (en paralelo).
import time
from uuid import uuid
import concurrent.futures
def mi_funcion_de_tiempo(num1, num2):
return num1 + num2
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
ids = ['Suma 1', 'Suma 2', 'Suma 3', 'Suma 4']
futuros = [
executor.submit(mi_funcion_de_tiempo, 2, 4),
executor.submit(mi_funcion_de_tiempo, 3, 5),
executor.submit(mi_funcion_de_tiempo, 1, 1),
executor.submit(mi_funcion_de_tiempo, 10, 20)
]
for future in concurrent.futures.as_completed(futuros):
try:
resultado = future.result()
print(f"Resultado: {resultado}")
except Exception as exc:
print('%r generated an exception: %s' % (future, exc))
Espero que te sea de ayuda.
Si tienes alguna duda, no dudes en dejarla en los comentarios.
{{ comments.length }} comentarios