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 协程的执行。

Future对象

Future 对象有如下两个作用。

  • 用来保存协程执行后的结果。

  • 用来和 Task 对象配合,调度执行协程。

Future 在 asyncio 库中相对来讲是比较低层级的对象,通常没有必要在应用层级代码中创建 Future 对象。