Certaines fonctions doivent s'exécuter de manière asynchrone sur le serveur Web. L'envoi d'e-mails ou le post-traitement de données sont des cas d'utilisation typiques.
Quelle est la meilleure façon (ou la plupart Pythonic) d’écrire une fonction de décorateur pour exécuter une fonction de manière asynchrone?
Ma configuration est courante: Python, Django, Gunicorn ou Waitress, AWS EC2 standard Linux
Par exemple, voici un début:
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
utilisation souhaitée:
@postpone
def foo():
pass #do stuff
J'ai continué à utiliser cette implémentation à grande échelle et en production sans aucun problème.
Définition du décorateur:
def start_new_thread(function):
def decorator(*args, **kwargs):
t = Thread(target = function, args=args, kwargs=kwargs)
t.daemon = True
t.start()
return decorator
Exemple d'utilisation:
@start_new_thread
def foo():
#do stuff
Au fil du temps, la pile s’est mise à jour et a fait la transition sans faute.
À l'origine, Python 2.4.7, Django 1.4, Gunicorn 0.17.2, maintenant Python 3.6, Django 2.1, Waitress 1.1.
Si vous utilisez des transactions de base de données, Django créera une nouvelle connexion qui doit être fermée manuellement:
from Django.db import connection
@postpone
def foo():
#do stuff
connection.close()
La manière la plus courante de traiter asynchrone dans Django consiste à utiliser Céleri et Django-celery
.
l'approche de tomcounsell fonctionne bien s'il n'y a pas trop de tâches entrantes. Si de nombreux travaux de longue durée sont exécutés sur une courte période, le processus principal en souffrira donc. Dans ce cas, vous pouvez utiliser un pool de threads avec une 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