使用 asyncawait 关键字

Changelog

Added in version 2.0.

如果安装 Flask 时包含了额外的 async 依赖(通过 pip install flask[async] 安装),那么路由、错误处理器、请求前后钩子(before request、after request)以及 teardown 函数都可以是协程函数。这使得视图可以使用 async def 定义,并使用 await

@app.route("/get-data")
async def get_data():
    data = await async_db_query(...)
    return jsonify(data)

可插拔的基于类的视图也支持使用协程实现的处理函数。这适用于继承自 flask.views.View 类的视图中的 dispatch_request() 方法,以及继承自 flask.views.MethodView 类的视图中的所有 HTTP 方法处理函数。

与 greenlet 一起使用 async

当使用 gevent 或 eventlet 提供应用服务或对运行环境打补丁时,要求 greenlet 版本不低于 1.0。使用 PyPy 时,要求 PyPy 版本不低于 7.3.7。

性能

异步函数需要事件循环来运行。Flask 作为一个 WSGI 应用,每个 worker 处理一个请求/响应周期。当请求到达一个异步视图时,Flask 会在线程中启动一个事件循环,在其中运行该视图函数,然后返回结果。

即使是异步视图,每个请求仍然会占用一个 worker。好处是你可以在视图中运行异步代码,例如并发进行多个数据库查询、向外部 API 发出 HTTP 请求等。但你应用能同时处理的请求数量并不会因此增加。

异步代码本身并不一定比同步代码更快。 异步适用于并发的 IO 密集型任务,但对 CPU 密集型任务通常没有帮助。传统的 Flask 视图对于大多数用例仍然适用,但 Flask 的异步支持让之前无法实现的一些代码成为可能。

后台任务

异步函数将在事件循环中运行直到完成,此时事件循环会停止。这意味着,在该异步函数完成时,任何未完成的额外任务都会被取消。因此,你不能通过 asyncio.create_task 等方式在视图中启动后台任务。

如果你想使用后台任务,最好通过任务队列来触发后台工作,而不是在视图函数中启动任务。考虑到这一点,你可以通过 ASGI 服务器运行 Flask,并使用 ASGI 中描述的 asgiref 的 WsgiToAsgi 适配器来启动 asyncio 任务。这样可行,是因为该适配器会创建一个持续运行的事件循环。

何时改用 Quart

由于实现方式的限制,Flask 的异步支持性能不如以异步为优先的框架。如果你的代码库主要是异步的,那么使用 Quart 会更合适。Quart 是基于 ASGI 标准重新实现的 Flask,而不是基于 WSGI,这使它可以在不需要多个 worker 或线程的情况下处理大量并发请求、长时间运行的请求和 WebSocket。

其实早就可以使用 Gevent 或 Eventlet 来运行 Flask,从而获得许多异步请求处理的优势。这些库是通过修改底层 Python 函数实现的,而 async/await 和 ASGI 则使用的是现代 Python 的标准特性。至于你应该使用 Flask、Quart,还是其他框架,最终取决于你项目的具体需求。

扩展

早于 Flask 异步支持的扩展通常不支持异步视图。如果这些扩展提供用于增强视图功能的装饰器,那么这些装饰器很可能无法与异步视图正常配合使用,因为它们不会等待(await)函数执行,也无法被等待。此外,这些扩展提供的其他函数也不会是可等待的,如果在异步视图中调用它们,很可能会造成阻塞。

扩展的作者可以通过使用 flask.Flask.ensure_sync() 方法来支持异步函数。例如,如果扩展提供一个视图函数装饰器,可以在调用被装饰函数之前使用 ensure_sync 包裹它,

def extension(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        ...  # Extension logic
        return current_app.ensure_sync(func)(*args, **kwargs)

    return wrapper

查看你打算使用的扩展的更新日志,看看是否已经实现了对异步的支持;如果没有,可以向他们提出功能请求或提交 PR。

其他事件循环

目前,Flask 仅支持 asyncio。你可以通过重写 flask.Flask.ensure_sync() 方法,修改异步函数的包装方式,从而使用其他异步库。