Wie kann ich dem Kolben einen Hintergrundfaden hinzufügen?

79

Ich bin damit beschäftigt, einen kleinen Spieleserver zu schreiben, um die Flasche auszuprobieren. Das Spiel stellt Benutzern eine API über REST zur Verfügung. Es ist für Benutzer einfach, Aktionen auszuführen und Daten abzufragen. Ich möchte jedoch die "Spielwelt" außerhalb der app.run () -Schleife bedienen, um Spielentitäten usw. zu aktualisieren. Angesichts der Tatsache, dass Flask so sauber implementiert ist, möchte ich um zu sehen, ob es einen Flask-Weg gibt, dies zu tun.

Marinus
quelle
Du meinst so etwas wie Flask-Admin? Wenn Sie ein ORM (SQL-Alchemy) verwenden, können Sie einfach eine neue Datenbanksitzung erstellen, um die Datenbank abzufragen, auch wenn die Anwendung ausgeführt wird.
Reptilien
Es sieht so aus, als gäbe es einen hackigen Weg , aber ich denke nicht, dass dies technisch unterstützt wird. Ich habe auch diese Antwort gefunden , die über die Verwendung von Kolben-Sellerie spricht.
Girasquid
Wenn Sie tatsächlich viel rechnen müssen, möchten Sie möglicherweise das Unterprozessmodul verwenden und einfach neue Prozesse erzeugen, um diese zusätzliche Berechnung durchzuführen.
Maus
@girasquid Einverstanden, Sellerie oder ein anderes Task-Queue-System ist ideal für diese Art von Dingen - Sie haben im Allgemeinen weniger Kontrolle über Threads oder Unterprozesse (da der übergeordnete Prozess möglicherweise vom Server ohne vorherige Ankündigung geerntet wird).
Sean Vieira
Dies ist ein Plan, jedoch wird der Unterprozess Datenstrukturen manipulieren, auf die Sie über die API für freiliegende Kolben zugreifen und diese einstellen möchten. Werde ich nicht auf Probleme stoßen?
Marinus

Antworten:

78

Ihre zusätzlichen Threads müssen von derselben App initiiert werden, die vom WSGI-Server aufgerufen wird.

Im folgenden Beispiel wird ein Hintergrundthread erstellt, der alle 5 Sekunden ausgeführt wird und Datenstrukturen bearbeitet, die auch für Flask-Routing-Funktionen verfügbar sind.

import threading
import atexit
from flask import Flask

POOL_TIME = 5 #Seconds

# variables that are accessible from anywhere
commonDataStruct = {}
# lock to control access to variable
dataLock = threading.Lock()
# thread handler
yourThread = threading.Thread()

def create_app():
    app = Flask(__name__)

    def interrupt():
        global yourThread
        yourThread.cancel()

    def doStuff():
        global commonDataStruct
        global yourThread
        with dataLock:
        # Do your stuff with commonDataStruct Here

        # Set the next thread to happen
        yourThread = threading.Timer(POOL_TIME, doStuff, ())
        yourThread.start()   

    def doStuffStart():
        # Do initialisation stuff here
        global yourThread
        # Create your thread
        yourThread = threading.Timer(POOL_TIME, doStuff, ())
        yourThread.start()

    # Initiate
    doStuffStart()
    # When you kill Flask (SIGTERM), clear the trigger for the next thread
    atexit.register(interrupt)
    return app

app = create_app()          

Nennen Sie es von Gunicorn mit so etwas:

gunicorn -b 0.0.0.0:5000 --log-config log.conf --pid=app.pid myfile:app
caio
quelle
14
Ich fand dies problematisch, wenn die Funktion zum automatischen Neuladen von flask verwendet wurde (bei jedem Neuladen wurde ein neuer Thread erstellt). Um dies zu beheben, habe ich werkzeug.serving.is_running_from_reloader verwendet , um es nur zu erstellen, wenn die App nicht vom Reloader ausgeführt wird.
Raffomania
2
@caio sollte es "mit dataLock:" Großbuchstabe L oben sein.
Jesse Sanford
Dies ist eine schöne Lösung; Hilft beim Umgang mit Kolben-Apps, die Multiprocessing- oder Threading-Module verwenden. Ich mag das.
Shan Valleru
2
Dieses Beispiel ist etwas verwirrend, da das mit "yourThread" erstellte Objekt kein Thread ist. Es ist ein Timer: Schlagen Sie vor, ihn umzubenennen. Und wenn yourTimer ausgeführt wird (in doStuff), weiß ich nicht, ob yourThread gültig ist, dh ob Sie Abbrechen auf einem Timer ausführen können, der nicht ausgeführt wurde. Es hat das Effizienzproblem, dass bei jeder Ausführung ein neues Objekt erstellt wird, falls dies ein Problem sein könnte.
Brian Bulkowski
1
Die korrekte Anweisung zum Überprüfen von "is_running_in_background ()" lautet wie folgt: Von werkzeug.serving import is_running_from_reloader if is_running_from_reloader () == False: startBackground ()
Brian Bulkowski
7

Neben der Verwendung von reinen Fäden oder der Sellerie-Warteschlange (beachten Sie, dass kein Flaschensellerie mehr benötigt wird) können Sie sich auch den Kolben-Apscheduler ansehen:

https://github.com/viniciuschiele/flask-apscheduler

Ein einfaches Beispiel, das von https://github.com/viniciuschiele/flask-apscheduler/blob/master/examples/jobs.py kopiert wurde :

from flask import Flask
from flask_apscheduler import APScheduler


class Config(object):
    JOBS = [
        {
            'id': 'job1',
            'func': 'jobs:job1',
            'args': (1, 2),
            'trigger': 'interval',
            'seconds': 10
        }
    ]

    SCHEDULER_API_ENABLED = True


def job1(a, b):
    print(str(a) + ' ' + str(b))

if __name__ == '__main__':
    app = Flask(__name__)
    app.config.from_object(Config())

    scheduler = APScheduler()
    # it is also possible to enable the API directly
    # scheduler.api_enabled = True
    scheduler.init_app(app)
    scheduler.start()

    app.run()
Andreas Bergström
quelle