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


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.