Gibt es in Flask eine Möglichkeit, die Antwort an den Client zu senden und dann die Verarbeitung fortzusetzen? Ich habe ein paar Buchhaltungsaufgaben zu erledigen, aber ich möchte den Kunden nicht warten lassen.

Beachten Sie, dass dies wirklich sehr schnelle Dinge sind, die ich tun möchte. Daher ist das Erstellen eines neuen Threads oder das Verwenden einer Warteschlange hier nicht wirklich angemessen. (Eines dieser schnellen Dinge ist das Hinzufügen von etwas zu einer Jobwarteschlange.)

25
edA-qa mort-ora-y 25 Juni 2013 im 13:09

4 Antworten

Beste Antwort

Klingt so, als ob Teardown Callbacks das unterstützen würden, was Sie wollen. Und Sie können es mit dem Muster von Rückrufen pro Anforderung nach Anforderung bis kombinieren Hilfe bei der Organisation des Codes.

4
Tommi Komulainen 26 Juni 2013 im 06:34

In Flask finden Sie ein Beispiel für die Verwendung von Sellerie hier https://gist.github.com/jzempel/3201722

Der Kern der Idee (Wortspiel beabsichtigt) besteht darin, die langen Buchhaltungsaufgaben als @ celery.task zu definieren und apply_async 1 oder Verzögerung aus der Ansicht heraus, um die Aufgabe zu starten

1
PuercoPop 25 Juni 2013 im 11:13

Teardown-Rückrufe werden leider nicht ausgeführt, nachdem die Antwort an den Client zurückgegeben wurde:

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

@app.teardown_request
def teardown(request):
    time.sleep(2)
    print("teardown_request")

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

if __name__ == "__main__":
    app.run()

Wenn Sie dies einrollen, werden Sie eine Verzögerung von 2 Sekunden feststellen, bevor die Antwort angezeigt wird, anstatt dass das Einrollen sofort endet und dann ein Protokoll 2 Sekunden später. Dies wird durch die Protokolle weiter bestätigt:

teardown_request
127.0.0.1 - - [25/Jun/2018 15:41:51] "GET / HTTP/1.1" 200 -

Die korrekte Ausführung nach Rückgabe einer Antwort besteht in der Verwendung der WSGI-Middleware, die der Methode des Antwortiterators schließen. Dies ist nicht ganz so einfach wie der Dekorator teardown_request, aber dennoch recht einfach:

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, start_response):
        iterator = self.application(environ, start_response)
        try:
            return ClosingIterator(iterator, [self.after_response_ext.flush])
        except Exception:
            traceback.print_exc()
            return iterator

Was Sie dann so verwenden können:

@app.after_response
def after():
    time.sleep(2)
    print("after_response")

In der Shell wird die Antwort sofort angezeigt, und 2 Sekunden später trifft after_response die Protokolle:

127.0.0.1 - - [25/Jun/2018 15:41:51] "GET / HTTP/1.1" 200 -
after_response

Dies ist eine Zusammenfassung einer früheren Antwort, die hier bereitgestellt wurde .

5
Matthew Story 9 Juli 2018 im 21:06

Ich hatte ein ähnliches Problem mit meinem Blog. Ich wollte Benachrichtigungs-E-Mails an diejenigen senden, die Kommentare abonniert haben, als ein neuer Kommentar veröffentlicht wurde, aber ich wollte nicht, dass die Person, die den Kommentar veröffentlicht, darauf wartet, dass alle E-Mails gesendet werden, bevor sie seine Antwort erhält.

Ich habe dafür ein multiprocessing.Pool verwendet. Ich habe einen Pool von einem Mitarbeiter gestartet (das war genug, Site mit geringem Datenverkehr) und jedes Mal, wenn ich eine E-Mail senden muss, bereite ich alles in der Funktion "Flaschenansicht" vor, leite aber den letzten send_email - Aufruf über { {X2}}.

3
Miguel 25 Juni 2013 im 22:47