Python Asyncio:Coroutines、Running、Awaitables 详解

Python 的 asyncio 库提供了基于事件循环的异步编程支持,核心围绕三个概念:协程(Coroutines)运行(Running)可等待对象(Awaitables)。理解它们能帮你写出高效的异步 I/O 代码。


1. 协程(Coroutines)

概念
协程是通过 async def 定义的函数。调用协程函数并不会立即执行其代码,而是返回一个协程对象(coroutine object)。协程对象必须被调度到事件循环中才能真正运行。

原理
协程底层基于 Python 的生成器(generator)机制,但使用了 async/await 语法糖。协程可以在 await 处主动挂起自身,让出控制权给事件循环,等待被等待的对象(如 I/O 操作)完成后恢复执行。这使得单线程能并发处理大量 I/O 任务。

示例

import asyncio

# 定义一个协程
async def say_hello():
    print("Hello,")
    await asyncio.sleep(1)   # 模拟 I/O 等待
    print("world!")

# 调用协程函数仅返回协程对象,不会执行
coro = say_hello()
print(coro)  # <coroutine object say_hello at 0x...>

2. 运行(Running)

概念
“运行” 指的是协程或任务被事件循环调度并执行的过程。通常使用 asyncio.run() 作为顶层入口,它会创建新的事件循环、运行传入的协程(直到完成),然后关闭循环。

此外,通过 asyncio.create_task() 可以将协程包装为 Task 对象,让它们在后台并发运行。

原理
事件循环负责维护一个就绪任务队列。当一个协程执行到 await 一个尚未完成的可等待对象时,该协程被挂起,事件循环切换到其他就绪的任务。当被等待的对象完成(例如 I/O 操作结束),事件循环会恢复等待它的协程。

示例

async def main():
    # 直接 await 一个协程(串行)
    await say_hello()

    # 并发运行多个任务
    task1 = asyncio.create_task(say_hello())
    task2 = asyncio.create_task(say_hello())
    await task1
    await task2

# 运行顶层协程
asyncio.run(main())

输出(两个任务并发,总耗时约1秒,而非2秒):

Hello,
Hello,
world!
world!

3. 可等待对象(Awaitables)

概念
await 后面跟着的对象必须是可等待(awaitable) 的。主要包括三种类型:
- 协程对象:由调用 async def 函数得到。
- Task 对象:通过 asyncio.create_task()asyncio.ensure_future() 创建,用于并发执行协程。
- Future 对象:底层抽象,表示一个尚未完成的操作。一般由 loop.create_future() 创建,很少直接使用,但 TaskFuture 的子类。

原理
await 一个可等待对象时,当前协程会挂起,并把控制权交还给事件循环。事件循环会监视该可等待对象的完成状态:
- 如果是协程对象,事件循环会将其包装为 Task 并开始执行。
- 如果是 TaskFuture,事件循环会等待其 set_result() 或异常被设置。

一旦可等待对象完成,事件循环会恢复 await 所在协程的执行,并返回结果(或抛出异常)。

示例

async def fetch_data():
    await asyncio.sleep(0.5)
    return {"data": 42}

async def use_awaitables():
    # 1. await 协程对象
    result = await fetch_data()
    print("协程对象结果:", result)

    # 2. await Task 对象
    task = asyncio.create_task(fetch_data())
    result2 = await task
    print("Task 结果:", result2)

    # 3. 手动创建 Future(不常用但展示原理)
    loop = asyncio.get_running_loop()
    future = loop.create_future()
    loop.call_later(0.5, future.set_result, "Future done")
    result3 = await future
    print("Future 结果:", result3)

asyncio.run(use_awaitables())

输出

协程对象结果: {'data': 42}
Task 结果: {'data': 42}
Future 结果: Future done

总结与关键点

概念 说明 典型操作
Coroutine async def 定义的函数,返回协程对象,可被 await coro = my_coro()
Running 通过事件循环调度协程,实现并发 asyncio.run(main()), create_task()
Awaitable 可被 await 的对象:协程、Task、Future await some_awaitable

原理核心
事件循环 + 协程的非阻塞挂起 → 单线程内完成大量 I/O 密集型任务的并发,避免线程切换开销。

注意事项
- 不能直接调用协程函数期待其运行,必须通过 awaitasyncio.create_task()
- asyncio.run() 在一个程序中只能调用一次(通常只用于主入口)。
- 异步代码中避免使用 time.sleep(),应使用 asyncio.sleep()