赞
踩
当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
当完成启动,但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)
当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
如果有更好的解决方案麻烦告诉我,谢谢~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。