asyncio标准库
Flask 异步编程是基于 Python 内置的 asyncio 模块的,因此我们只有学懂了 asyncio 的相关概念和基本用法后,才能更好地理解 Flask 中的异步编程。
下面围绕事件循环、协程、Task 对象、Future 对象等概念讲解 asyncio 的工作机制。
Python 3.8 版本的 asyncio 库在 Windows 系统上有一个 bug,如果在运行时出现 ValueError:set_wakeup_fd only works in main thread 错误,升级到 Python 3.9 版本即可解决。 |
事件循环
在非阻塞代码执行过程中,为了在事件发生时能准确地执行回调,需要不断轮询是否有事件发生。这个轮询等待事件的过程,被称为事件循环。在 asyncio 库中,一般情况下不需要自己手动创建事件循环,通过 asyncio.run 运行协程便会自动创建事件循环。
协程
从 Python 3.5 版本添加 async 和 await 关键字以来,只要以 async def 开头定义的函数,都叫作协程。协程在执行中可以被挂起和恢复,因此可以用于构建异步程序。驱动协程执行必须使用 await 关键字,如以下代码。
import asyncio
async def nested():
asyncio.sleep(1)
return 42
async def main():
# 仅创建了一个协程对象,但是并没有执行
nested()
# 使用await关键字,驱动并等待协程对象执行
result = await nested()
print(result)
# 创建一个事件循环并执行main协程
asyncio.run(main())
python
在上述代码中,nested() 函数仅是创建一个协程,await nested() 函数才是驱动并等待协程的执行。最外层的 main 协程不能直接执行,需要通过 asyncio.run 方法执行,该方法会创建一个事件循环并执行协程。
Task(任务)对象
Task 是用来调度协程挂起和恢复的。协程本身只能通过同步的方式执行,如果要并行执行多个协程,则必须将协程封装为 Task 对象。在 asyncio 库中,可以通过 asyncio.gather 方法将多个协程并行执行,asyncio.gather 方法内部会自动将协程封装为Task,无须手动封装。并行执行任务的示例代码如下。
async def my_worker(index):
await asyncio.sleep(1)
print("worker %d" % index)
async def main():
tasks = []
for x in range(1, 6):
tasks.append(my_worker(x))
await asyncio.gather(*tasks)
python
上述代码中,在 main 协程中创建了 5 个 my_worker 协程对象并添加到列表中,然后统一传给 asyncio.gather 方法并行执行。上述 main 协程只需 1s 即可完成 5 个 my_worker 协程的执行。