Python Async IO - async/await 关键字
在学习asyncio之前,先理清楚同步/异步的概念:
同步是指完成事务的逻辑,先执行第一个事务,如果阻塞了,会一直等待,直到这个事务完成,再执行第二个事务,顺序执行
异步是和同步相对的,异步是指在处理调用这个事务的之后,不会等待这个事务的处理结果,直接处理第二个事务去了,通过状态、通知、回调来通知调用者处理结果
asyncio函数:
异步IO采用消息循环的模式,重复“读取消息—处理消息”的过程,也就是说异步IO模型”需要一个消息循环,在消息循环中,主线程不断地重复“读取消息-处理消息”这一过程。
event_loop 事件循环:
程序开启一个无限的循环,程序员会把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。
coroutine 协程:
协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
task 任务:
一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。
async/await 关键字:
用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。
asyncio 的运行逻辑
首先我们先看一段代码
import asyncio
async def main():
print('hello')
await asyncio.sleep(1)
print('word')
coro = main()
直接运行的结果如下
sys:1: RuntimeWarning: coroutine 'main' was never awaited
你会发现函数并没有执行,而是返回给我们一个 Warning ,这是由于 async def 定义的 coroutine function 其返回的对象是一个 coroutine object,直接调用并不会直接运行函数内部的代码
如果要运行这个 coroutine object 那就必须完成
- 进入 async 模式,也就是 event_loop() 事件循环, 事件循环会安排协同程序的执行
- 把 coroutine object 变成一个 task
import asyncio
async def main():
print('hello')
await asyncio.sleep(1)
print('word')
coro = main()
asyncio.run(coro)
结果如下
hello
word
也就是说,coro 这个 coroutine object 会 先通过 asyncio.run 进入 event_loop() 变成第一个 task,同时 event_loop() 开始查找可以运行的 task,然后开始执行 coro 这个 task
多任务运行逻辑
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
结果如下
started at 14:50:55
hello
world
finished at 14:50:58
在开始理解代码前,首先要了解
- asyncio event_loop 中最小的运行单位是 task,当 coroutine 变为 task 时,控制权交还给 event loop 进行调度
- await coroutine --> 像调用生成器一样调用coroutine,等待直到拿到范围值(可能为空),不交还控制权,导致并不能实现异步
- await task --> 阻塞程序,并将控制权交还event_loop
- create_task --> 提供了一种将单个coroutine包装成task的方法,注册到event_loop,不交还控制权
然后开始代码
- 进入 event_loop 后 main 函数变为 task 开始执行,控制权交还 event_loop
- main 函数继续执行
- 执行到第一个 await say_after(1, 'hello') 进入函数内部,未交还控制权
- 执行完后执行第二个 await say_after(2, 'world')
总共耗时 3s,如果要同步执行,那就要用到 asyncio.create_task()
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
await task1
await task2
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
结果如下
started at 15:07:05
hello
hello
finished at 15:07:07
如何取返回值
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
return f"{what} - {delay}"
async def main():
print(f"started at {time.strftime('%X')}")
# method 1
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
ret1 = await task1
ret2 = await task2
# method 2
ret3 = await asyncio.gather(task1, task2)
# method 3
ret4 = await asyncio.gather(
say_after(1, 'hello'),
say_after(2, 'world')
)
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())