Multihilo para Python Django

Algunas funciones deben ejecutarse de forma asíncrona en el servidor web. El envío de correos electrónicos o el posprocesamiento de datos son casos de uso típicos.

¿Cuál es la mejor manera (o la mayoría de las formas pythonicas) de escribir una función decoradora para ejecutar una función de forma asíncrona?

Mi configuración es común: Python, Django, Gunicorn o Waitress, estándar de AWS EC2 Linux

Por ejemplo, aquí hay un comienzo:

from threading import Thread def postpone(function): def decorator(*args, **kwargs): t = Thread(target = function, args=args, kwargs=kwargs) t.daemon = True t.start() return decorator 

uso deseado:

 @postpone def foo(): pass #do stuff 

He seguido utilizando esta implementación a escala y en producción sin problemas.

Definición de decorador:

 def start_new_thread(function): def decorator(*args, **kwargs): t = Thread(target = function, args=args, kwargs=kwargs) t.daemon = True t.start() return decorator 

Ejemplo de uso:

 @start_new_thread def foo(): #do stuff 

Con el tiempo, la stack se ha actualizado y transicionado sin fallar.

Originalmente Python 2.4.7, Django 1.4, Gunicorn 0.17.2, ahora Python 3.6, Django 2.1, Waitress 1.1.

Si está utilizando alguna transacción de base de datos, Django creará una nueva conexión y esto debe cerrarse manualmente:

 from django.db import connection @postpone def foo(): #do stuff connection.close() 

El apio es una cola de tareas / cola de tareas asíncronas. Está bien documentado y es perfecto para lo que necesitas. Te sugiero que comiences aqui

La forma más común de realizar un procesamiento asíncrono en Django es usar Celery y django-celery .

El enfoque de tomcounsell funciona bien si no hay demasiados trabajos entrantes. Si se ejecutan muchos trabajos de larga duración en un corto período de tiempo, por lo tanto, se generan muchos hilos, el proceso principal se verá afectado. En este caso, puede utilizar un grupo de subprocesos con una coroutine,

 # in my_utils.py from concurrent.futures import ThreadPoolExecutor MAX_THREADS = 10 def run_thread_pool(): """ Note that this is not a normal function, but a coroutine. All jobs are enqueued first before executed and there can be no more than 10 threads that run at any time point. """ with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor: while True: func, args, kwargs = yield executor.submit(func, *args, **kwargs) pool_wrapper = run_thread_pool() # Advance the coroutine to the first yield (priming) next(pool_wrapper) 
 from my_utils import pool_wrapper def job(*args, **kwargs): # do something def handle(request): # make args and kwargs pool_wrapper.send((job, args, kwargs)) # return a response