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() 创建,很少直接使用,但 Task 是 Future 的子类。
原理
当 await 一个可等待对象时,当前协程会挂起,并把控制权交还给事件循环。事件循环会监视该可等待对象的完成状态:
- 如果是协程对象,事件循环会将其包装为 Task 并开始执行。
- 如果是 Task 或 Future,事件循环会等待其 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 密集型任务的并发,避免线程切换开销。
注意事项:
- 不能直接调用协程函数期待其运行,必须通过 await 或 asyncio.create_task()。
- asyncio.run() 在一个程序中只能调用一次(通常只用于主入口)。
- 异步代码中避免使用 time.sleep(),应使用 asyncio.sleep()。
评论
请登录后发表评论
暂无评论,快来发表第一条评论吧!