python multithreading flask

python - Ejecuta una función después de que Flask devuelve la respuesta



multithreading (4)

Flask es una aplicación WSGI y, como resultado, fundamentalmente no puede manejar nada después de la respuesta. Esta es la razón por la que no existe tal controlador, la aplicación WSGI en sí misma es responsable solo de construir el objeto iterador de respuesta para el servidor WSGI.

Sin embargo, un servidor WSGI (como gunicorn ) puede proporcionar muy fácilmente esta funcionalidad, pero vincular la aplicación al servidor es una muy mala idea por varias razones.

Por esta razón exacta, WSGI proporciona una especificación para Middleware , y Werkzeug proporciona una serie de ayudantes para simplificar la funcionalidad común de Middleware. Entre ellos se encuentra una clase ClosingIterator que le permite conectar métodos al método de close del iterador de respuesta que se ejecuta después de que se cierra la solicitud.

Aquí hay un ejemplo de una implementación ingenua de after_response realizada como una extensión de Flask:

import traceback from werkzeug.wsgi import ClosingIterator class AfterResponse: def __init__(self, app=None): self.callbacks = [] if app: self.init_app(app) def __call__(self, callback): self.callbacks.append(callback) return callback def init_app(self, app): # install extension app.after_response = self # install middleware app.wsgi_app = AfterResponseMiddleware(app.wsgi_app, self) def flush(self): for fn in self.callbacks: try: fn() except Exception: traceback.print_exc() class AfterResponseMiddleware: def __init__(self, application, after_response_ext): self.application = application self.after_response_ext = after_response_ext def __call__(self, environ, after_response): iterator = self.application(environ, after_response) try: return ClosingIterator(iterator, [self.after_response_ext.flush]) except Exception: traceback.print_exc() return iterator

Puedes usar esta extensión así:

import flask app = flask.Flask("after_response") AfterResponse(app) @app.after_response def say_hi(): print("hi") @app.route("/") def home(): return "Success!/n"

Cuando encrespes "/" verás lo siguiente en tus registros:

127.0.0.1 - - [24/Jun/2018 19:30:48] "GET / HTTP/1.1" 200 - hi

Esto resuelve el problema simplemente sin introducir hilos (GIL ??) o tener que instalar y administrar una cola de tareas y software de cliente.

Tengo un código que debe ejecutarse después de que Flask devuelva una respuesta. No creo que sea lo suficientemente complejo como para configurar una cola de tareas como Celery. El requisito clave es que Flask debe devolver la respuesta al cliente antes de ejecutar esta función. No puede esperar a que se ejecute la función.

Hay algunas preguntas existentes sobre esto, pero ninguna de las respuestas parece abordar la ejecución de una tarea después de que la respuesta se envía al cliente, todavía se ejecutan sincrónicamente y luego se devuelve la respuesta.


La larga historia corta es que Flask no proporciona ninguna capacidad especial para lograr esto. Para tareas simples de una sola vez, considere el multiproceso de Python como se muestra a continuación. Para configuraciones más complejas, use una cola de tareas como RQ o Celery.

¿Por qué?

Es importante comprender las funciones que proporciona Flask y por qué no logran el objetivo previsto. Todos estos son útiles en otros casos y son una buena lectura, pero no ayudan con las tareas de fondo.

after_request de Flask

El controlador after_request de Flask, como se detalla en este patrón para devoluciones de llamada de solicitud diferida y este fragmento al adjuntar diferentes funciones por solicitud , pasará la solicitud a la función de devolución de llamada. El caso de uso previsto es modificar la solicitud , como adjuntar una cookie.

Por lo tanto, la solicitud esperará a que estos manejadores terminen de ejecutarse porque se espera que la solicitud en sí misma cambie como resultado.

Controlador de teardown_request de teardown_request de frasco

Esto es similar a after_request , pero teardown_request no recibe el objeto de request . Eso significa que no esperará la solicitud, ¿verdad?

Esta parece ser la solución, como sugiere esta respuesta a una pregunta similar de . Y dado que la documentación de Flask explica que las devoluciones de llamada de desmontaje son independientes de la solicitud real y no reciben el contexto de la solicitud, tendría buenas razones para creerlo.

Desafortunadamente, teardown_request todavía es sincrónico, solo ocurre en una parte posterior del manejo de solicitudes de Flask cuando la solicitud ya no es modificable. Flask aún esperará a que se completen las funciones de desmontaje antes de devolver la respuesta, como dicta esta lista de devoluciones de llamadas y errores de Flask .

Las respuestas de transmisión de Flask

Flask puede transmitir respuestas pasando un generador a Response() , como sugiere esta respuesta de a una pregunta similar .

Con la transmisión, el cliente comienza a recibir la respuesta antes de que finalice la solicitud. Sin embargo, la solicitud aún se ejecuta sincrónicamente, por lo que el trabajador que maneja la solicitud está ocupado hasta que finalice la secuencia.

Este patrón de matraz para la transmisión incluye cierta documentación sobre el uso de stream_with_context() , que es necesario para incluir el contexto de la solicitud.

Entonces, ¿cuál es la solución?

Flask no ofrece una solución para ejecutar funciones en segundo plano porque esto no es responsabilidad de Flask.

En la mayoría de los casos, la mejor manera de resolver este problema es usar una cola de tareas como RQ o Celery. Estos manejan cosas difíciles como la configuración, la programación y la distribución de trabajadores para usted. Esta es la respuesta más común a este tipo de preguntas porque es la más correcta y lo obliga a configurar las cosas de una manera que considere el contexto, etc. correctamente.

Si necesita ejecutar una función en segundo plano y no desea configurar una cola para administrar esto, puede usar el threading o multiprocessing integrado de Python para generar un trabajador en segundo plano.

No puede acceder a la request ni a otros locales de subprocesos de Flask desde tareas en segundo plano, ya que la solicitud no estará activa allí. En su lugar, pase los datos que necesita de la vista al hilo de fondo cuando lo cree.

@app.route(''/start_task'') def start_task(): def do_work(value): # do something that takes a long time import time time.sleep(20) thread = Thread(target=do_work, kwargs={''value'': request.args.get(''value'', 20)) thread.start() return ''started''


Puedes usar este código, lo he probado. Funciona.

este código imprimirá la cadena "mensaje". después de los 3 segundos, desde el tiempo de programación. Puede cambiar el tiempo de acuerdo con sus requisitos.

import time, traceback import threading def every(delay,message, task): next_time = time.time() + delay time.sleep(max(0, next_time - time.time())) task(message) def foo(message): print(message+" :foo", time.time()) def main(message): threading.Thread(target=lambda: every(3,message, foo)).start() main("message")


Solución de middleware para planos de matraces

Esta es la misma solución propuesta por Matthew Story (que es la solución perfecta en mi humilde opinión, gracias Matthew), adaptada para Flask Blueprints. La salsa secreta aquí es obtener el contexto de la aplicación utilizando el proxy current_app. Lea here para obtener más información ( here )

Supongamos que las clases AfterThisResponse y AfterThisResponseMiddleware se colocan en un módulo en .utils.after_this_response.py

Entonces, cuando se produce la creación del objeto Flask, es posible que tenga, por ejemplo ...

__init__.py

from api.routes import my_blueprint from .utils.after_this_response import AfterThisResponse app = Flask( __name__ ) AfterThisResponse( app ) app.register_blueprint( my_blueprint.mod )

Y luego en su módulo de planos ...

a_blueprint.py

from flask import Blueprint, current_app mod = Blueprint( ''a_blueprint'', __name__, url_prefix=URL_PREFIX ) @mod.route( "/some_resource", methods=[''GET'', ''POST''] ) def some_resource(): # do some stuff here if you want @current_app.after_this_response def post_process(): # this will occur after you finish processing the route & return (below): time.sleep(2) print("after_response") # do more stuff here if you like & then return like so: return "Success!/n"