asyncio
什么是coroutine
说到 coroutine 一般会指的是两个东西 coroutine function 和 coroutine object 在以下代码中,async def main 就是一个 coroutine function,它定义了一个 coroutine 的过程
1 | import asyncio |
当调用一个 coroutine function 时,返回的是一个 coroutine object,并不会运行 coroutine function 里面的代码。
如果要运行一个 coroutine function 里面的代码,需要做以下的事
- 进入 async 模式,也就是 event loop控制整个程序的状态
- 把 coroutine 变成task
如何进入async模式
我们把正常代码的模式称为 sync 模式,从 sync 模式切换到 async 模式,切换到让 event loop 控制一切,基本上只用一个入口函数 asyncio.run
asyncio.run的参数是一个 coroutine, asyncio.run 的调用会做两件事情
- 建立起event loop
- 将传给他的 coroutine 变成 建立的 event loop 里面的第一个 task
当 event loop 建立后,他就会开始寻找有哪些 task 可以执行,此时 event loop 里面只有 刚创建出来的 task,event loop 开始执行这个 task
尝试运行上面的**创建coro **代码块,输出如下
1 | python async_example.py |
event loop 调度多个 task
event loop 的核心就是有多个 task,由他来决定接下来运行哪个task,接下来尝试往 event loop 里面加入多个 task
如何运行一个coroutine object
使用 await
1 | import asyncio |
在上面的代码块中,say_after 函数是一个 coroutine function ,asyncio.sleep 也是一个 coroutine function ,调用 say_after 会返回一个 coroutine,但是里面的代码不会被执行,对coroutine 使用await 发生了如下的事情
- 调用 coroutine,返回一个 future
- await 这个future,提取返回值
await future是一个同步的操作,等待future的返回后才继续往下执行
尝试运行以上代码,输出结果应该如下
1 | python async_example2.py |
运行的整个过程如下
- 将main作为一个task,放到event loop,event loop 运行这个task
- print started at
- 运行 say_after(1, ‘hello’) 这个 coroutine function 得到 coroutine object,await这个 coroutine object,得到future ( say_after1)
- await future ( say_after1)运行,得到返回值
- 等待1秒,print(‘hello’),future ( say_after1)完成
- 运行 say_after(2, ‘world’) 这个 coroutine function 得到 coroutine object,await这个 coroutine object,得到future ( say_after2)
- await future ( say_after2)运行,得到返回值
- 等待2秒,print(‘world’),future ( say_after1)完成
- print finished at
从上面的过程中可以看出, say_after(1, ‘hello’) say_after(2, ‘world’) 是同步的,一共sleep了3秒,那有没有办法能让他们一共sleep 2秒,也就是说,让他们一起等
在运行过程中,我们可以发现,如果要让他们一起等,那就要让 event loop 一起发现这2个task
使用 create_task
1 | import asyncio |
使用 create_task,发生了如下的事情
- 将一个 coroutine object 变成 task
- 将task注册给event loop
await 一个task,与await 一个 coroutine object 不同,他执行如下步骤
- 告诉event loop,当前的task 需要 依赖这个task完成,完成后提取返回值
- 交还控制权给event loop
运行过程如下
- 将main作为一个task,放到event loop,event loop 运行这个task
- print start at
- 运行 say_after(1, ‘hello’) 这个 coroutine function 得到 coroutine object,使用 create_task 将 coroutine object 变成 task,并且将task注册到event loop
- 运行 say_after(2, ‘world’) 这个 coroutine function 得到 coroutine object,使用 create_task 将 coroutine object 变成 task,并且将task注册到event loop
- await task1,告诉event loop,当前的的task(main) 需要等待 task1 完成
- await task2,告诉event loop,当前的的task(main) 需要等待 task2 完成
- event loop 检查task,有3个task,[main, task1, task2],main需要等待 task1,task2 完成,尝试运行task1
- task2 运行完成,将控制权交给event loop
- event loop 检查task,有2个task,[main, task2],main需要等待 ,task2 完成,尝试运行task2
- task2 运行完成,将控制权交给event loop
- event loop 检查task,有1个task,[main],尝试运行main
- main 运行完成
- 退出
在整个过程中控制权的交回是显式的,也就是说,event loop不能强行从task拿回控制权,所以如果task中有死循环,整个event loop 就卡死了,交回控制权的方式有两种
- await 一个 coroutine object
- 函数完成 return
运行结果如下
1 | python async_example3.py |
如果不 await task2 ,虽然 task2 已经加入了event loop,但是在 task1 结束后 main task 也结束了,所以没有机会执行到 task2 的输出
gather
1 | import asyncio |
为了方便,当需要一起创建多个task时,可以使用gather,gather的返回时是future
gather 参数可以是task,future也可以是coroutine object,当参数是coroutine object时,他会把coroutine object变成task
对gather await,等待会所有的task都完成,把他们的返回值放入一个list,list中值的顺序就是task的顺序
所以以上代码块的输出如下
1 | python async_example4.py |
await coro与await task的区别
使用 await 一个 coroutine object 会直接执行这个coroutine ,而不是把控制权交给event loop
1 | import asyncio |
get_1中的形式,不会把控制权交还给event loop
总结
- evnet loop控制task执行,task没办法控制event loop接下来执行哪个task,只能告诉event loop我需要等待哪个 task 完成
- coroutine object 是变成 task 才能运行的
- task 一旦开始运行,就需要显式的把任务交还给 event loop
所以同时刻实际上只有一段代码在运行,他只是想办法利用代码中间的等待时间,所以如果代码中没有等待,协程对代码是没有帮助的