当前位置:   article > 正文

flask异步任务

flask异步任务

flask程序有三种状态

1.程序设置状态

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

  1. def create_app(config_name=None):
  2. app = Flask('test_flask')
  3. if config_name is None:
  4. config_name = os.getenv('FLASK_CONFIG', 'development')
  5. app.config.from_object(config[config_name])
  6. app.register_blueprint(test_bp)
  7. db.init_app(app)
  8. async_task.init_app(app)
  9. return app

2.程序运行状态

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

  1. E:\python_code\test_flask\.venv\Scripts\python.exe -m flask run
  2. * Serving Flask app "test_flask" (lazy loading)
  3. * Environment: development
  4. * Debug mode: on
  5. * Restarting with stat
  6. * Debugger is active!
  7. * Debugger PIN: 108-954-734
  8. * 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项目合适的地方实例化扩展

  1. from Flask_Threadpool import FlaskThreadPool
  2. async_task = FlaskThreadPool()

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

  1. def create_app(config_name=None):
  2. app = Flask('test_flask')
  3. ...
  4. async_task.init_app(app)
  5. return app

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

  1. def callback(future):
  2. e = future.exception()
  3. print("异步任务的错误:" + str(e))
  4. @async_task.submit(callback)
  5. def long_task():
  6. current_app.logger.info("任务启动...")
  7. ...
  8. current_app.logger.info("任务结束")
  9. @test_bp.route('/starttask', methods=['GET', 'POST'])
  10. def start_task():
  11. long_task()
  12. return "SUCCESS"

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

  1. from flask import Flask
  2. from concurrent.futures import ThreadPoolExecutor
  3. class FlaskThreadPool:
  4. """
  5. 1.在项目合适的地方实例化
  6. ft_pool = FlaskThreadPool()
  7. 2.在项目合适的地方初始化
  8. ft_pool.init_app(app)
  9. 3.装饰一个预期异步执行的任务函数
  10. @ft_pool.submit(callback)
  11. def func(args1, args2):
  12. ...
  13. """
  14. def __init__(self, app=None):
  15. if app is not None:
  16. self.init_app(app)
  17. def init_app(self, app: Flask):
  18. """ 完成初始化 """
  19. # 将扩展加入到扩展字典
  20. self.app = app
  21. if not hasattr(app, "extensions"):
  22. app.extensions = {}
  23. app.extensions["async_task"] = self
  24. size = app.config.setdefault('THREAD_POOL_SIZE', 4)
  25. self.executor = ThreadPoolExecutor(size)
  26. def submit(self, callback=None):
  27. """ 装饰器:将任务异步执行
  28. 1.先将任务加入上下文环境
  29. 2.将任务变成异步任务
  30. """
  31. def async_task(func):
  32. def inner(*args, **kwargs):
  33. self.executor.submit(func, *args, **kwargs).add_done_callback(callback)
  34. return inner
  35. def decorator(func):
  36. @async_task
  37. def inner(*args, **kwargs):
  38. with self.app.app_context():
  39. func(*args, **kwargs)
  40. return inner
  41. return decorator

that's all

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/414242
推荐阅读
相关标签
  

闽ICP备14008679号