使用 async
和 await
¶
变更日志
2.0 版新增。
如果使用 async
额外功能(pip install flask[async]
)安装 Flask,则路由、错误处理程序、请求前、请求后和终止函数都可以是协程函数。这允许使用 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 方法处理程序。
在 Python 3.8 上的 Windows 上使用 async
Python 3.8 有一个与 Windows 上的 asyncio 相关的错误。如果您遇到类似 ValueError: set_wakeup_fd only works in main thread
的错误,请升级到 Python 3.9。
将 async
与 greenlet 一起使用
在使用 gevent 或 eventlet 为应用程序提供服务或修补运行时时,需要 greenlet>=1.0。在使用 PyPy 时,需要 PyPy>=7.3.7。
性能¶
异步函数需要一个事件循环来运行。Flask 作为 WSGI 应用程序,使用一个工作进程来处理一个请求/响应周期。当一个请求进入异步视图时,Flask 将在某个线程中启动一个事件循环,在那里运行视图函数,然后返回结果。
即使对于异步视图,每个请求仍然会占用一个工作进程。好处是您可以在视图中运行异步代码,例如同时进行多个数据库查询、对外部 API 进行 HTTP 请求等。但是,您的应用程序一次可以处理的请求数量将保持不变。
异步本身并不比同步代码快。当执行并发 IO 绑定任务时,异步是有益的,但可能不会改善 CPU 绑定任务。传统的 Flask 视图仍然适用于大多数用例,但 Flask 的异步支持使编写和使用以前本机无法实现的代码成为可能。
后台任务¶
异步函数将在事件循环中运行,直到它们完成,届时事件循环将停止。这意味着当异步函数完成时,任何附加的已生成任务(尚未完成)都将被取消。因此,您无法生成后台任务,例如通过 asyncio.create_task
。
如果您希望使用后台任务,最好使用任务队列来触发后台工作,而不是在视图函数中生成任务。考虑到这一点,您可以通过使用 ASGI 服务器提供 Flask 并利用 asgiref WsgiToAsgi 适配器(如 ASGI 中所述)来生成 asyncio 任务。这样做是因为该适配器创建了一个持续运行的事件循环。
何时使用 Quart¶
由于 Flask 的异步支持的实现方式,因此其性能不如优先异步的框架。如果您主要使用异步代码库,那么考虑使用 Quart 是明智的。Quart 是基于 ASGI 标准(而不是 WSGI)重新实现的 Flask。这使其能够处理许多并发请求、长期运行的请求和 websocket,而无需多个工作进程或线程。
使用 Gevent 或 Eventlet 运行 Flask 也已成为可能,以获得异步请求处理的许多好处。这些库修补了低级 Python 函数以实现此目的,而 async
/ await
和 ASGI 使用标准的、现代的 Python 功能。最终,您应该使用 Flask、Quart 还是其他框架取决于您对项目特定需求的理解。
扩展¶
早于 Flask 异步支持的 Flask 扩展不期待异步视图。如果它们提供装饰器以向视图添加功能,那么这些装饰器可能无法与异步视图配合使用,因为它们不会等待函数或可等待。它们提供的其他函数也不会可等待,并且如果在异步视图中调用,它们可能会阻塞。
扩展作者可以通过利用 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()
来更改异步函数的包装方式,以使用不同的库。