深入理解协程分为三部分进行讲解:
协程的引入
yield from 实现异步协程
async/await 实现异步协程
本篇为深入理解协程系列文章的最后一篇.
从本篇你将了解到:
async/await 的使用.
如何从 yield from 风格的协程修改为 async/await 风格.
篇幅较长, 请耐心阅读.
async/await 的引入
上篇[yield from 实现异步协程] https://mp.weixin.qq.com/s/kfnrgr5SrEH4uzMqpDoxEA 我们引入了 asynico 模块, 结合 yield from 实现异步协程. 但语法不够简洁, 其中涉及的生成器, 装饰器也让人头疼不已.
为了语法更加简洁. 于是, 在 Python3.5(PEP 492)中新增了 async/await 语法来实现异步协程.
async/await 的使用
先介绍几个概念:
async/await :python3.5 之后用于定义协程的关键字, async 定义一个协程, await 用于挂起阻塞的异步调用接口.
event_loop : 事件循环, 程序开启一个无限的循环, 程序员会把一些函数注册到事件循环上. 当满足事件发生的时候, 调用相应的协程函数.
coroutine : 协程对象, 指一个使用 async 关键字定义的函数, 它的调用不会立即执行函数, 而是会返回一个协程对象. 协程对象需要注册到事件循环, 由事件循环调用.
task : 任务, 是对协程进一步封装, 其中包含任务的各种状态.
future: 代表将来执行或没有执行的任务的结果. 它和 task 上没有本质的区别
1. 创建协程
在 def 前加上 async 的声明, 就完成了一个协程函数的定义. 协程函数不能直接调用运行, 需要将协程注册到事件循环, 并启动事件循环才能使用.
- import asyncio
- async def fun(a): # 定义协程函数
- print(a)
- # 调用协程函数, 生成一个协程对象, 此时协程函数并未执行
- coroutine = fun('hello world')
- # 创建事件循环
- loop = asyncio.get_event_loop()
- # 将协程函数添加到事件循环, 并启动
- loop.run_until_complete(coroutine)
- # 输出
- hello Word
2. 任务对象 task
协程对象不能直接运行, 在注册事件循环的时候, 其实是 run_until_complete 方法将协程包装成为了一个任务 (task) 对象. 我们也可以显式实现它.
实现方式 1:
使用 create_task()创建 task.
- import asyncio
- async def fun(a):
- print(a)
- return a
- coroutine = fun('hello world')
- loop = asyncio.get_event_loop()
- # 使用 create_task()创建 task, 并将 coroutine 对象转化成 task 对象
- task = loop.create_task(coroutine)
- print(f'task: {task}')
- loop.run_until_complete(task)
- print(f'task: {task}')
- print(f'result: {result}')
输出结果:
- task: <Task pending coro=<fun() running at D:/test.py:3>>
- hello world
- task: <Task finished coro=<fun() done, defined at D:/test.py:3> result='hello world'>
从输出结果能看到, 创建 task 对象后, 未将 task 添加到事件循环之前, 状态是 pending;task 对象执行完毕后, 状态是 finished, 并将参数 a 的值返回.
实现方式 2:
使用 asyncio 的 ensure_future() 方法, 创建 task.
- import asyncio
- async def fun(a):
- print(a)
- return a
- coroutine = fun('hello world')
- # 使用 asyncio 的 ensure_future() 方法, 创建 task, 并将 coroutine 对象转化成 task 对象
- task = asyncio.ensure_future(coroutine)
- loop = asyncio.get_event_loop()
- print(f'task: {task}')
- loop.run_until_complete(task)
- print(f'task: {task}')
输出结果:
- task: <Task pending coro=<fun() running at D:/test.py:3>>
- hello world
- task: <Task finished coro=<fun() done, defined at D:/test.py:3> result='hello world'>
通过 ensure_future() 可以在 loop 未定义前创建 task. 实现效果与上面相同.
3. 绑定回调函数
如果需要在 task 执行完毕后对结果进行处理, 可以通过给 task 绑定回调函数完成, 回调的最后一个参数是 future 对象(如 task 对象).
- import asyncio
- async def fun(a):
- print(a)
- return a
- def callback(task): # 回调函数, 打印 task 的返回值
- print(f'result: {task.result()}')
- coroutine = fun('hello world')
- loop = asyncio.get_event_loop()
- task = loop.create_task(coroutine)
- task.add_done_callback(callback) #绑定回调函数
- print(f'task: {task}')
- loop.run_until_complete(task)
- print(f'task: {task}')
输出结果:
task: <Task pending coro=<fun() running at D:/test.py:3> cb=[callback() at D:/Study/Python/python_text / 非项目 / 协程. py:7]>
- hello world
- result: hello world # 完成了返回值的打印
- task: <Task finished coro=<fun() done, defined at D:/test.py:3> result='hello world'>
4. 多任务协程
如果我们需要执行多个任务时, 我们可以定义一个任务列表, 并将需要完成的协程任务都加进去. 将原本的 loop.run_until_complete(tasks)改为 loop.run_until_complete(asyncio.wait(tasks)).
如果执行的是多个耗时的任务, 如网络请求, 文件读取等. 此时就 await 就派上用场了, await 可以针对耗时的操作进行挂起, 就像生成器里的 yield 一样, 函数让出控制权. 协程遇到 await, 事件循环将会挂起该协程, 执行别的协程, 直到其他的协程也挂起或者执行完毕, 再进行下一个协程的执行.
举个例子:
- import time
- import asyncio
- async def taskIO_1():
- print('开始运行 IO 任务 1...')
- await asyncio.sleep(2)
- print('IO 任务 1 已完成, 耗时 2s')
- return taskIO_1.__name__
- async def taskIO_2():
- print('开始运行 IO 任务 2...')
- await asyncio.sleep(3)
- print('IO 任务 2 已完成, 耗时 3s')
- return taskIO_2.__name__
- if __name__ == '__main__':
- start = time.time()
- loop = asyncio.get_event_loop()
- tasks = [taskIO_1(), taskIO_2()]
- loop.run_until_complete(asyncio.wait(tasks)) # 完成事件循环, 直到最后一个任务结束
- print('所有 IO 任务总耗时 %.5f 秒' % float(time.time()-start))
- # 输出
开始运行 IO 任务 2...
开始运行 IO 任务 1...
IO 任务 1 已完成, 耗时 2s
IO 任务 2 已完成, 耗时 3s
所有 IO 任务总耗时 3.00251 秒
可以看出, 原本需要 5 秒, 现在执行只需要 3 秒.
yield from 转 async/await
上述代码有没有很眼熟.
其实, 这段代码正是[yield from 实现异步协程] https://mp.weixin.qq.com/s/kfnrgr5SrEH4uzMqpDoxEA 末尾, yield from 结合 asynico 实现异步协程的代码. 只是将 yielf from 风格变为 async/await 风格.
由于 async/await 与 yield from 风格的协程底层实现方式相同. 因此, 从 yield from 风格改为 async/await 风格非常容易. 只需:
把 @asyncio.coroutine 替换为 async;
把 yield from 替换为 await.
async/await 风格的代码隐藏了装饰器, yield from 语法, 方便了人们的理解, 同时也让代码更加简洁.
来源: https://www.cnblogs.com/ghostlee/p/12190056.html