asyncio 简介#

asyncio 是 Python 标准库的一部分,它提供了一个带有 IO(输入/输出)操作的事件循环。它的存在是为了在 Python 中允许并发编程,事件循环在之前的任务等待 IO 时切换到另一个任务。这种并发性允许更高的 CPU 利用率,从而带来更高的吞吐量性能。

理解这一点最简单的方法是考虑一些具体的东西,比如一个演示模拟。在下面,我们使用模拟的 IO 延迟获取一个 url,

import asyncio


async def simulated_fetch(url, delay):
    await asyncio.sleep(delay)
    print(f"Fetched {url} after {delay}")
    return f"<html>{url}"


def main():
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(asyncio.gather(
        simulated_fetch('http://google.com', 2),
        simulated_fetch('http://bbc.co.uk', 1),
    ))
    print(results)

你应该看到以下输出,

>>> Fetched http://bbc.co.uk after 1
>>> Fetched http://google.com after 2
>>> ['<html>http://google.com', '<html>http://bbc.co.uk']

这表明尽管首先调用了 google.com 获取,但 bbc.co.uk 实际上先完成,即代码并发运行。此外,代码在不到 2 秒的时间内运行,而不是像同步代码那样超过 3 秒。

与 Web 服务器的相关性#

Web 服务器本质上进行 IO,因为它们接收和响应来自网络的请求。这意味着 asyncio 非常适合,即使框架内的代码本身不执行 IO。然而在实践中 IO 是存在的,例如从文件加载模板,或者联系数据库或其他服务器。

常见陷阱#

很容易等待错误的东西,例如,

await awaitable.attribute

不会像你期望的那样等待 awaitable 对象,而是尝试解析属性并等待它。这在以下情况中很常见,

await request.form.get('key')

这会导致错误,因为协程包装器没有 get 属性。为了解决这个问题,只需使用括号来指示首先需要等待的内容,

(await awaitable).attribute
(await request.form).get('key')