Выполните функцию после того, как Flask возвращает ответ

У меня есть код, который нужно выполнить после Flask возвращает ответ. Я не думаю, что это достаточно сложно, чтобы создать очередь задач, такую как сельдерей. Ключевым требованием является то, что Flask должен вернуть ответ клиенту перед запуском этой функции. Он не может дождаться выполнения функции.

есть некоторые существующие вопросы об этом, но ни один из ответов, похоже, не адресует выполнение задачи после отправки ответа клиенту, они все еще выполните синхронно, а затем ответ будет возвращен.

3 ответов


короче говоря, фляга не предоставляет никаких специальных возможностей для этого. Для простых одноразовых задач рассмотрите многопоточность Python, как показано ниже. Для более сложных конфигураций используйте очередь задач, например RQ или сельдерей.

почему?

важно понимать функции, которые предоставляет колба, и почему они не достичь намеченной цели. Все это полезно в других случаях и хорошо читается, но не помогает с фоновыми задачами.

фляги!--1--> проводник

колбы after_request обработчик, как описано в этот шаблон для отложенных обратных вызовов запросов и этот фрагмент при подключении различных функций по запросу, передаст запрос функции обратного вызова. Предполагаемый вариант использования -модифицировать запрос, например, чтобы прикрепить cookie.

таким образом, запрос будет ждать, пока эти обработчики закончат выполнение, поскольку ожидается, что в результате изменится сам запрос.

фляги!--3--> проводник

это похоже на after_request, а teardown_request не появляется по-прежнему будет ждать разрыв функции завершить перед возвращением ответа, как этот список обратных вызовов колбы и ошибок диктат.

фляги ответы

колба может передавать ответы, передавая генератор в Response(), as это переполнение стека ответ на подобный вопрос предполагает.

С потоковой передачей, клиент тут начните получать ответ до завершения запроса. Однако запрос по-прежнему выполняется синхронно, поэтому работник, обрабатывающий запрос, занят до завершения потока.

этот шаблон колбы для потоковой передачи включает в себя некоторая документация по использованию stream_with_context(), что необходимо для включения контекста запроса.

Итак, каково решение?

Flask не предлагает решение для запуска функций в фоновом режиме, потому что это не ответственность Flask.

в большинстве случаев лучший способ решить эту проблему-использовать очередь задач, такую как RQ или сельдерей. Они управляют сложными вещами, такими как конфигурация, планирование и распределение рабочих для вас.Это самый распространенный ответ к этому типу вопросов, потому что это наиболее правильно, и заставляет вас настроить вещи таким образом, где вы рассматриваете контекст и т. д. правильно.

Если вам нужно запустить функцию в фоновом режиме и не хотите настраивать очередь для управления этим, вы можете использовать встроенный Python threading или multiprocessing для создания фонового рабочего.

вы не можете получить доступ request или другие из местных жителей потока Flask из фоновых задач, так как запрос там не будет активности. Вместо этого передайте необходимые данные из представления в фоновый поток при его создании.

@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'

фляга-это WSGI app и в результате он принципиально не может ничего обрабатывать после ответа. Вот почему такого обработчика не существует, само приложение WSGI отвечает только за построение объекта итератора ответа серверу WSGI.

A сервер WSGI однако (как gunicorn) может очень легко обеспечить эту функциональность, но привязка приложения к серверу-очень плохая идея для ряда причины.

по этой причине WSGI предоставляет спецификацию для промежуточное ПО, и Werkzeug предоставляет ряд помощников для упрощения общих функций промежуточного ПО. Среди них есть ClosingIterator класс, который позволяет подключать методы до close метод итератора ответа, который выполняется после закрытия запроса.

вот пример наивного after_response реализация выполнена в виде колбы расширение:

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

вы можете использовать это расширение, как это:

import flask
app = flask.Flask("after_response")
AfterResponse(app)

@app.after_response
def say_hi():
    print("hi")

@app.route("/")
def home():
    return "Success!\n"

когда вы curl "/" вы увидите следующее в логах:

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

Это решает проблему просто без введения каких-либо потоков (GIL??) или необходимость установки и управления очередью задач и клиентским программным обеспечением.


вы можете использовать этот код, который я пробовал.Это работает.

этот код напечатает строку "message". после 3 секунд, от времени планирования. Вы можете изменить время вашей собственной личности согласно вам требование.

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")