Python中async协程快速理解
1. async
关键字:定义协程
-
当你在一个函数定义前加上
async def
时,你就创建了一个 协程函数。 -
协程函数被调用时,它不会立即执行里面的代码,而是返回一个 协程对象。这个协程对象是一个可等待 (awaitable) 的对象。
-
示例:
async def my_coroutine():print("开始执行协程")await asyncio.sleep(1) # 模拟耗时操作print("协程执行完毕")
2. await
关键字:暂停与等待
await
关键字只能在async
函数内部使用。- 当一个协程遇到
await
表达式时,它会 暂停 当前的执行,并 交出控制权 给事件循环。 - 事件循环会去执行其他可执行的任务(比如其他协程)。
- 一旦
await
等待的那个可等待对象(通常是另一个协程或一个异步操作)完成,事件循环会 恢复 之前暂停的协程,从await
语句的下一行继续执行。 - 关键点:
await
是非阻塞的。它不会像传统的同步函数调用那样一直等待直到完成。相反,它允许程序在等待期间做其他事情。
3. 事件循环 (Event Loop):异步的核心
-
事件循环是异步编程的 核心调度器。它负责管理和运行协程。
-
它的主要职责是:
- 注册 协程和其他异步任务。
- 调度 协程的执行。
- 监听 事件(如I/O完成、定时器到期等)。
- 当一个协程被
await
暂停时,事件循环会切换到另一个准备好运行的协程。 - 当被等待的操作完成时,事件循环会将相应的协程重新标记为“可运行”,并在合适的时机恢复其执行。
-
在 Python 中,通常使用
asyncio
库来获取和运行事件循环。
asyncio.run(main_coroutine())
是启动事件循环并运行顶层协程的常见方式。
4. 执行流程总结 (以 asyncio.run()
为例):
-
定义协程函数: 使用
async def
定义一个或多个协程函数。 -
创建协程对象:
在某个地方(通常是主函数或另一个协程中)调用协程函数,这将创建协程对象。
Python
async def main():task1 = asyncio.create_task(my_coroutine_1()) # 创建任务task2 = asyncio.create_task(my_coroutine_2())await task1 # 等待任务完成await task2
-
启动事件循环:
通过
asyncio.run(顶层协程对象)
来启动事件循环。
asyncio.run()
会创建一个新的事件循环,运行传入的顶层协程直到完成,然后关闭事件循环。
-
协程的执行与暂停:
- 事件循环开始运行顶层协程。
- 当顶层协程遇到
await
表达式时,它会将控制权交还给事件循环。 - 事件循环会检查是否有其他协程或异步任务可以运行。如果有,它会切换到这些任务。
- 当被
await
等待的操作完成时,事件循环会收到通知,并将之前暂停的协程重新标记为可运行。 - 在下一次调度时,事件循环会恢复该协程的执行,从
await
的下一行继续。
-
循环往复: 这个过程会不断重复,直到所有任务都完成,事件循环才会停止。
5. 示例代码与流程分析:
import asyncio
import timeasync def task_a():print(f"Task A: 开始 at {time.strftime('%X')}")await asyncio.sleep(2) # 模拟I/O操作,耗时2秒print(f"Task A: 结束 at {time.strftime('%X')}")return "Result A"async def task_b():print(f"Task B: 开始 at {time.strftime('%X')}")await asyncio.sleep(1) # 模拟I/O操作,耗时1秒print(f"Task B: 结束 at {time.strftime('%X')}")return "Result B"async def main():print(f"Main: 启动 at {time.strftime('%X')}")# 创建任务,此时协程并没有立即执行# asyncio.create_task() 将协程包装成一个 Task 对象,并提交给事件循环task1 = asyncio.create_task(task_a())task2 = asyncio.create_task(task_b())print(f"Main: 任务已创建 at {time.strftime('%X')}")# await 等待任务完成。注意,这里的等待是非阻塞的result1 = await task1result2 = await task2print(f"Main: 所有任务完成 at {time.strftime('%X')}")print(f"Result A: {result1}")print(f"Result B: {result2}")if __name__ == "__main__":asyncio.run(main())
执行流程分析:
-
asyncio.run(main())
启动事件循环,并开始执行main
协程。 -
main
协程打印 “Main: 启动…”。 -
asyncio.create_task(task_a())
和asyncio.create_task(task_b())
创建了task_a
和task_b
两个协程的 Task 对象,并将它们提交给事件循环。此时task_a
和task_b
并没有立即执行。 -
main
协程打印 “Main: 任务已创建…”。 -
await task1
:main
协程遇到await
,暂停执行,将控制权交回事件循环。 -
事件循环发现
task_a
和
task_b
已经准备好运行。它会先运行
task_a
(或
task_b
,取决于调度器的内部实现,通常是按照提交顺序或优先级)。
- 假设先运行
task_a
:task_a
打印 “Task A: 开始…”。 task_a
遇到await asyncio.sleep(2)
,暂停执行,将控制权交回事件循环。
- 假设先运行
-
事件循环发现
task_b
可以运行。
task_b
打印 “Task B: 开始…”。task_b
遇到await asyncio.sleep(1)
,暂停执行,将控制权交回事件循环。
-
现在,事件循环正在等待
asyncio.sleep(2)
(来自task_a
) 和asyncio.sleep(1)
(来自task_b
) 完成。 -
1秒钟后,
asyncio.sleep(1)
(来自
task_b
) 完成。事件循环恢复
task_b
的执行。
task_b
打印 “Task B: 结束…”。task_b
执行完毕。
-
2秒钟后(从
task_a
开始算起),
asyncio.sleep(2)
(来自
task_a
) 完成。事件循环恢复
task_a
的执行。
task_a
打印 “Task A: 结束…”。task_a
执行完毕。
-
现在,
task1
和task2
都已经完成。事件循环回到main
协程被暂停的地方。 -
main
协程恢复执行,从result1 = await task1
获取task_a
的结果。 -
然后从
result2 = await task2
获取task_b
的结果。 -
main
协程打印 “Main: 所有任务完成…” 和最终结果。 -
main
协程执行完毕,事件循环关闭。
关键 takeaway:
async
和await
并不是多线程或多进程。它们在单个线程中实现并发,通过在await
点之间切换来完成。await
是协作式的多任务。一个协程必须明确地await
另一个可等待对象才能交出控制权。- 事件循环是异步编程的“大脑”,负责调度和管理协程的执行。