烧瓶应用与背景线程

时间:2014-03-24 16:44:22

标签: multithreading flask

我正在创建一个烧瓶应用程序,对于一个请求,我需要运行一些长时间运行的作业,这不需要在UI上等待。我将创建一个线程并向UI发送消息。该线程将计算并更新数据库。但是,UI会在提交时看到一条消息。 下面是我的实现,但它正在运行线程,然后将输出发送到UI,这不是我喜欢的。如何在后台运行此线程?

@app.route('/someJob')
def index():
    t1 = threading.Thread(target=long_running_job)
    t1.start()
    return 'Scheduled a job'

def long_running_job
    #some long running processing here

如何让线程t1运行后台并立即发送消息呢?

6 个答案:

答案 0 :(得分:45)

试试这个例子,在Python 3.4.3 / Flask 0.11.1上进行测试

from flask import Flask
from time import sleep
from concurrent.futures import ThreadPoolExecutor

# DOCS https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor
executor = ThreadPoolExecutor(2)

app = Flask(__name__)


@app.route('/jobs')
def run_jobs():
    executor.submit(some_long_task1)
    executor.submit(some_long_task2, 'hello', 123)
    return 'Two jobs was launched in background!'


def some_long_task1():
    print("Task #1 started!")
    sleep(10)
    print("Task #1 is done!")


def some_long_task2(arg1, arg2):
    print("Task #2 started with args: %s %s!" % (arg1, arg2))
    sleep(5)
    print("Task #2 is done!")


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

答案 1 :(得分:4)

对于这样的事情,最好的办法是使用消息代理。在python世界中有一些优秀的软件就是为了做到这一点:

两者都是很好的选择。

以您执行此操作的方式生成线程几乎不是一个好主意,因为这会导致处理传入请求的问题,等等。

如果您看一下芹菜或RQ入门指南,他们会以正确的方式指导您完成此操作!

答案 2 :(得分:0)

如果您想在烧瓶应用程序上下文中执行长时间运行的操作,那么它会更容易(而不是使用ThreadPoolExecutor,照顾例外):

  1. 为您的应用程序定义命令行(cli.py) - 因为所有Web应用程序都应该有一个管理员cli
  2. subprocess.Popen(无需等待)Web请求中的命令行。
  3. 例如:

    # cli.py
    
    import click
    import yourpackage.app
    import yourpackage.domain
    
    app = yourpackage.app.create_app()
    
    @click.group()
    def cli():
        pass
    
    @click.command()
    @click.argument('foo_id')
    def do_something(foo_id):
        with app.app_context():
            yourpackage.domain.do_something(foo_id)
    
    if __name__ == '__main__':
        cli.add_command(do_something)
        cli()
    

    然后,

    # admin.py (flask view / controller)
    
    bp = Blueprint('admin', __name__, url_prefix='/admin')
    
    @bp.route('/do-something/<int:foo_id>', methods=["POST"])
    @roles_required('admin')
    def do_something(foo_id):
        yourpackage.domain.process_wrapper_do_something(foo_id)
        flash("Something has started.", "info")
        return redirect(url_for("..."))
    

    # domain.py
    
    import subprocess
    
    def process_wrapper_do_something(foo_id):
        command = ["python3", "-m", "yourpackage.cli", "do_something", str(foo_id)]
        subprocess.Popen(command)
    
    def do_something(foo_id):
        print("I am doing something.")
        print("This takes some time.")
    

答案 3 :(得分:0)

签出Flask-Executor,它在后台使用并发功能,使您的生活变得非常轻松。

from flask_executor import Executor

executor = Executor(app)

@app.route('/someJob')
def index():
    executor.submit(long_running_job)
    return 'Scheduled a job'

def long_running_job
    #some long running processing here

这不仅可以在后台运行作业,还可以使他们访问应用程序上下文。它还提供了一种存储作业的方式,以便用户可以签回以获取状态。

答案 4 :(得分:0)

同意@rdegges 的标记答案。抱歉,我的帐户没有足够的信用来在答案下添加评论,但我想明确说明“为什么要使用消息代理,而不是生成线程(或进程)”。

关于 ThreadPoolExecutor 和 flask_executor 的其他答案是创建一个新线程(或进程,因为flask_executor 能够)来执行“long_running_job”。这些新线程/进程将与主网站具有相同的上下文:

对于线程:如果该线程引发异常,新线程将能够访问网站应用程序的上下文、更改内容或破坏它; 对于流程:新流程将拥有网站应用程序上下文的副本。如果网站在初始化时以某种方式使用了大量内存,新进程也会有一份它的副本,即使该进程不会使用这部分内存。

另一方面,如果您使用消息代理和另一个应用程序来检索工作消息以对其进行处理,则新应用程序将与网站应用程序无关,也不会从中复制内存网络应用。

以后,当你的应用足够大时,你可以把你的应用放到另一个服务器(或多个服务器)上,很容易横向扩展。

答案 5 :(得分:0)

如果你想用芹菜和烧瓶。首先检查您的操作,并在操作完成后重定向您的 celery 任务。 如果你现在没有芹菜,你可以检查这个链接:FLASK: How to establish connection with mysql server?