如何向烧瓶添加后台线程?


78

我正在忙着编写一个小型游戏服务器来试用Flask。游戏通过REST向用户展示API。用户执行操作和查询数据很容易,但是我想在app.run()循环之外为“游戏世界”提供服务,以更新游戏实体等。鉴于Flask的实现如此简洁,我想看看是否有烧瓶的方法可以做到这一点。


您是说类似Flask-Admin?或者,如果您使用的是ORM(SQL-Alchemy),则即使应用程序正在运行,也可以创建一个新的数据库会话来查询数据库。
reptilicus

看起来这是一种骇人听闻的方法,但是我不认为这在技术上受到支持。我也找到了这个答案,它谈到了使用烧瓶芹菜。
girasquid 2013年

如果您实际上需要进行大量计算,则可能需要使用子流程模块,并简单地生成新流程来进行该额外的计算。
Maus

@girasquid同意的,celery或其他任务队列系统非常适合这种情况-通常,您对线程或子进程的控制较少(因为父进程可能会被服务器占用,恕不另行通知)。
肖恩·维埃拉

那是一个计划,但是子过程将操纵您想要通过公开的flask API访问和设置的数据结构。我不会遇到问题吗?
Marinus

Answers:


78

您的其他线程必须从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
我发现在使用flask的自动重新加载功能时会出现问题(每次重新加载都会创建一个新线程)。为了解决这个问题,我使用了werkzeug.serving.is_running_from_reloader仅在应用程序未从重新加载器运行时创建它。
raffomania's

2
@caio应该在上面带有“ with dataLock:”的大写字母L中。
杰西·桑福德

这是一个很好的解决方案。帮助处理使用多处理或线程模块的flask应用程序。我喜欢。
Shan Valleru

2
此示例有些混乱,因为创建的名为“ yourThread”的对象不是线程。这是一个计时器:建议您重命名。而且,当执行yourTimer时(在doStuff中),我不知道yourThread是否有效-即,是否可以在尚未执行的Timer上执行cancel。它存在效率问题,即每次执行时都会创建一个新对象。
Brian Bulkowski

1
检查“ is_running_in_background()”的正确语句是这样的:从werkzeug.serving导入is_running_from_reloader if is_running_from_reloader()== False:startBackground()
Brian Bulkowski

7

除了使用纯线程或Celery队列(请注意不再需要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()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.