flask异步任务

flask程序有三种状态

1.程序设置状态

当flask应用实例被创建后,应用处于程序设置状态,此时所有的全局对象都没有被绑定。就像下面第二行代码,app被创建,但是配置类还没加载,蓝图还没注册,数据库扩展以及其他的各种扩展也还没来得及初始化,此时应用对象是一个“干净”的app

def create_app(config_name=None):
    app = Flask('test_flask')
    if config_name is None:
        config_name = os.getenv('FLASK_CONFIG', 'development')
    app.config.from_object(config[config_name])
    app.register_blueprint(test_bp)
    db.init_app(app)
    async_task.init_app(app)
    return app

2.程序运行状态

当完成启动,但API还没被访问时,flask应用处于程序运行状态。此时程序上下文对象current_app和g都绑定了各自的对象,之后才可以正常使用。下图为程序运行状态的日志

E:\python_code\test_flask\.venv\Scripts\python.exe -m flask run
 * Serving Flask app "test_flask" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 108-954-734
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

3.请求运行状态

当API被访问,也就是我们写的视图函数被执行时,flask应用处于请求运行状态,请求上下文对象request和session会才会被绑定。而请求运行状态被激活时,程序运行状态必定会被激活,所以我们才可以在视图中正常使用程序上下文和请求上下文

无上下文的异常

上下文其实很好理解,就像小时候写作文一样:一个句子只有放到它的上下文环境中,表达的意思才通顺。同样的道理,flask应用中某个函数也需要它的上下文环境。句子离开上下文可能语义不清,函数离开上下文环境就会报错,如下就是我碰到的一个无上下文环境报错的日志

 No application found. Either work inside a view function or push an application context. See http://flask-sqlalchemy.pocoo.org/contexts/.

场景

我需要在API视图中执行一段比较耗时的代码,最后将结果写入到数据库。为了不让API超时,就需要引入异步任务的机制:将API执行的任务封装出来,然后异步执行。

处理办法

在网上搜了一下,码友的方法大致分为两派:1.使用线程池,2.使用celery(实现代码很简单我就不写了)。两种方法其实都可以实现python异步任务,但是在flask有些场景需要上下文环境,异步出去后上下文就随着视图的return走了,再执行任务直接就被干掉,最后被add_done_callback捕获错误。

带上下文的异步任务的扩展

在flask项目合适的地方实例化扩展

from Flask_Threadpool import FlaskThreadPool

async_task = FlaskThreadPool()

在flask项目合适的地方初始化扩展实例

def create_app(config_name=None):
    app = Flask('test_flask')
    ...
    async_task.init_app(app)
    return app

在需要异步执行的方法上添加装饰器

def callback(future):
    e = future.exception()
    print("异步任务的错误:" + str(e))


@async_task.submit(callback)
def long_task():
    current_app.logger.info("任务启动...")
    ...
    current_app.logger.info("任务结束")


@test_bp.route('/starttask', methods=['GET', 'POST'])
def start_task():
    long_task()
    return "SUCCESS"


前面用到的Flask_Threadpool扩展就是下面这个

from flask import Flask
from concurrent.futures import ThreadPoolExecutor


class FlaskThreadPool:
    """
    1.在项目合适的地方实例化
        ft_pool = FlaskThreadPool()
    2.在项目合适的地方初始化
        ft_pool.init_app(app)
    3.装饰一个预期异步执行的任务函数
        @ft_pool.submit(callback)
        def func(args1, args2):
            ...
    """
    def __init__(self, app=None):
        if app is not None:
            self.init_app(app)

    def init_app(self, app: Flask):
        """ 完成初始化 """
        # 将扩展加入到扩展字典
        self.app = app
        if not hasattr(app, "extensions"):
            app.extensions = {}
        app.extensions["async_task"] = self
        size = app.config.setdefault('THREAD_POOL_SIZE', 4)
        self.executor = ThreadPoolExecutor(size)

    def submit(self, callback=None):
        """ 装饰器:将任务异步执行
        1.先将任务加入上下文环境
        2.将任务变成异步任务
        """
        def async_task(func):
            def inner(*args, **kwargs):
                self.executor.submit(func, *args, **kwargs).add_done_callback(callback)
            return inner

        def decorator(func):
            @async_task
            def inner(*args, **kwargs):
                with self.app.app_context():
                    func(*args, **kwargs)
            return inner
        return decorator

that's all

如果有更好的解决方案麻烦告诉我,谢谢~

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值