Python 的异步 I/O
转载自 Python 的异步 I/O
异步 I/O(Asynchronous Input/Output) 是一种 不阻塞程序执行 的 I/O 操作方式。发起 I/O 操作后,不必等待操作完成,程序可以继续做其他事情;当操作完成时,通过回调、事件循环或协程机制通知程序处理结果。
协程
Python 的异步 I/O 基于协程实现。使用async关键字来创建一个异步函数,对异步函数的调用不会执行该函数,而是生成一个协程对象。
对每一个协程对象,都必须等待其结束(即使是没有启动的协程),否则会产生一个 RuntimeWarning 类型的异常。
示例 :
1# 创建一个异步函数 2async def say_hello(): 3 print("hello world") 4 5# 创建协程 6coro = say_hello() 7print("协程对象", coro)
要启动一个协程,有三种方式:
- 通过
asyncio.run运行一个协程对象 - 使用
await关键字,这种方法只能在另一个async函数中才能使用 - 通过
asyncio.create_task
await必须在async函数中才能使用,因此无法启动协程的顶层入口点(此时只能使用asyncio.run函数)await让出当前协程,将控制权交给调度器;当前协程进入 等待 状态,直到目标完成后恢复就绪
- 如果
await的目标是一个协程,则创建该协程的Task,将其加入调度器并立即执行- 如果
await的目标不是一个协程(例如asyncio.create_task创建 的Task),则由事件循环(EventLoop)从就绪队列中选择一个协程运行
asyncio.create_task创建目标协程的Task并将其加入调度器
- 当前协程不会让出,从而可以连续启动多个协程,实现并发执行
- 返回的
Task对象也可以在适当的时候使用await等待其结束。
await 示例
1import asyncio 2import time 3 4async def say_hello(): 5 print("hello", time.strftime('%X')) 6 await asyncio.sleep(1) 7 print("hello", time.strftime('%X')) 8 9async def say_world(): 10 print("world", time.strftime('%X')) 11 await asyncio.sleep(1) 12 print("world", time.strftime('%X')) 13 14# 顶层入口点 15async def main(): 16 await say_hello() # 启动say_hello()返回的协程,并等待其结束 17 await say_world() # 要等到前一个await结束后,才会启动 18 19# 启动顶层入口点 20asyncio.run(main())
运行结果 :
1hello 15:27:26 2hello 15:27:27 3world 15:27:27 4world 15:27:28
asyncio.create_task 示例
1import asyncio 2import time 3 4async def say_hello(): 5 print("hello", time.strftime('%X')) 6 await asyncio.sleep(1) 7 print("hello", time.strftime('%X')) 8 9async def say_world(): 10 print("world", time.strftime('%X')) 11 await asyncio.sleep(1) 12 print("world", time.strftime('%X')) 13 14# 顶层入口点 15async def main(): 16 task_say_hello = asyncio.create_task(say_hello()) # 启动协程不等待 17 task_say_world = asyncio.create_task(say_world()) 18 19 await task_say_hello 20 await task_say_world 21 22# 启动顶层入口点 23asyncio.run(main())
运行结果 :
1hello 15:29:41 2world 15:29:41 3hello 15:29:42 4world 15:29:42
对比
通过上面两个示例打印的顺序和时间可以看出 await 和 asyncio.create_task 的区别:
await创建一个协程的Task时,前协程让出并进入等待状态,需要目标协程运行完毕,当前协程才能恢复就绪,因此运行流程是串行的asyncio.create_task创建一个协程的Task时,当前协程不会让出(而是后续await时让出),因此运行流程是并发的。