Як я можу додати фоновий потік до колби?


79

Я зайнятий написанням невеликого ігрового сервера для випробування фляги. Гра надає користувачам API через REST. Користувачам легко виконувати дії та запитувати дані, однак я хотів би обслуговувати "ігровий світ" за межами циклу app.run () для оновлення ігрових сутностей тощо. Враховуючи те, що Flask настільки чітко реалізований, я хотів би щоб перевірити, чи є для цього спосіб Flask.


Ви маєте на увазі щось на зразок Flask-Admin? Або якщо ви використовуєте ORM (SQL-алхімія), ви можете просто створити новий сеанс баз даних для запиту бази даних, навіть якщо програма запущена.
reptilicus

Схоже, існує шалений спосіб зробити це , але я не думаю, що це технічно підтримується. Я також знайшов цю відповідь , яка говорить про використання для цього колби-селери.
girasquid

Якщо вам насправді потрібно зробити багато обчислень, можливо, ви захочете використовувати модуль підпроцесу і просто породити нові процеси, щоб зробити це додаткове обчислення.
Maus

@girasquid Погоджено, селера або якась інша система черги завдань ідеально підходить для такого роду - у вас, як правило, менше контролю над потоками або підпроцесами (оскільки батьківський процес може бути зібраний сервером без попередження).
Шон Вієйра,

Це план, однак підпроцес буде маніпулювати структурами даних, до яких ви хочете отримати доступ і встановити через api відкритої колби. Чи не зіткнуся з проблемами?
Марінус

Відповіді:


80

Ваші додаткові потоки повинні ініціюватися з тієї самої програми, яка викликається сервером WSGI.

Наведений нижче приклад створює фоновий потік, який виконується кожні 5 секунд і маніпулює структурами даних, які також доступні для маршрутизованих функцій Flask.

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

Назвіть це від Gunicorn приблизно так:

gunicorn -b 0.0.0.0:5000 --log-config log.conf --pid=app.pid myfile:app

14
Я виявив, що це проблематично при використанні функцій автоперезавантаження колби (при кожному перезавантаженні створювався новий потік). Щоб виправити це, я використав werkzeug.serving.is_running_from_reloader, щоб створити його лише тоді, коли програма не запускається з перезавантажувача.
raffomania

2
@caio це повинно бути "з dataLock:" великим L вгорі.
Джессі Санфорд,

Це гарне рішення; допомагає мати справу з програмами-колбами, які використовують багатопроцесорні або потокові модулі. Мені це подобається.
Шан Валлеру

2
Цей приклад трохи заплутаний, оскільки створений об’єкт під назвою „yourThread” не є потоком. Це таймер: запропонуйте перейменувати його. І, коли yourTimer виконується (у doStuff), я не знаю, чи є вашThread дійсним - тобто, чи можете ви виконати скасування на таймері, який не був виконаний. Проблема ефективності полягає в тому, що при кожному виконанні створюється новий об’єкт, якщо це може бути проблемою.
Брайан Булковскі

1
Правильне твердження для перевірки "is_running_in_background ()" має такий вигляд: from werkzeug.serving import is_running_from_reloader if is_running_from_reloader () == False: startBackground ()
Brian Bulkowski

7

На додаток до використання чистих ниток або черги на селеру (зауважте, що flask-celery більше не потрібно), ви також можете поглянути на flask-apscheduler:

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

Простий приклад, скопійований з https://github.com/viniciuschiele/flask-apscheduler/blob/master/examples/jobs.py :

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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.