一、前言
之前写过 asynico 异步编程的文章,写那篇博客的时候 python 最新官方版本是3.6+。几个月后发布了 python3.7,这次版本更新对 asynico 改动挺大的,官方推出了一套 高层级的API,其实就是封装了原来那套低层级的API。
python 通过协程来实现异步编程,因此我们首先来了解下协程。
二、协程
1)协程通过 async/await 语法进行声明,使用 asyncio.run() 函数执行协程,此函数会运行传入的协程,负责管理 asyncio 事件循环,终结异步生成器,并关闭线程池。如下代码所示:
import asyncio,datetime async def main(): print(datetime.datetime.now()) await asyncio.sleep(1) print(datetime.datetime.now()) if __name__ =='__main__': asyncio.run(main())
2)如果一个对象想在 await 语句中使用,那么它必须是 可等待 对象。许多 asyncio API 都被设计为接受可等待对象,可等待 对象有三种主要类型:协程、任务 和 Future。
- 协程
Python中的协程也是可等待对象,因此可被 await。如 price 协程方法:
import asyncio async def price(x,y): return x*y async def main(): P = await price(1,2) print(P) if __name__ =='__main__': asyncio.run(main())
- 任务
当协程通过 asyncio.create_task()方法 被封装成一个任务,并自行调度执行任务,该方法 python 3.7版本被加入,低版本使用 asyncio.ensure_future() 。
import asyncio
async def price(x,y): return x*y async def main(): task = asyncio.create_task(price(1,2)) P = await task print(P) if __name__ =='__main__': asyncio.run(main())
- Future
Future 是一种特殊的 低层级 可等待对象,表示一个异步操作的最终结果。官网 不建议在应用层级的代码中创建 Future 对象,因此没有深入了解。
三、并发任务
线程在遇到IO等待时,会被阻塞,并 交出CPU控制权,换句话来说由 系统控制线程切换。协程遇到IO等待时,则由 程序控制,不会被阻塞,也 不会交出CPU控制权,而是直接执行下一个事务。所以协程在处理IO密集型任务时,资源开销小,效率极高,优于多线程。
使用 asyncio.gather(*aws) 并发运行协程,如果所有可等待对象都成功完成,结果将是一个由所有返回值聚合而成的列表。结果值的顺序与 aws 中可等待对象的顺序一致。
import asyncio,datetime async def price(x,y): await asyncio.sleep(1) print(datetime.datetime.now()) return x*y async def main(): P = await asyncio.gather(price(1,2), price(2,4)) print(P) if __name__ =='__main__': asyncio.run(main()) #输出 #2021-11-17 20:11:17.180417 #2021-11-17 20:11:17.180417 #[2, 8]
低层级 API 和 高层级 API 代码对比
- 低层级 API 代码,如下是 python 3.7 以下版本 实现并发的代码:
import time import asyncio from aiohttp import ClientSession tasks = [] url = "https://www.baidu.com/{}" async def hello(url): async with ClientSession() as session: async with session.get(url) as response: # print(response) print('Hello World:%s' % time.time()) return await response.read() def main(): for i in range(5): task = asyncio.ensure_future(hello(url.format(i))) tasks.append(task) result = loop.run_until_complete(asyncio.gather(*tasks)) print(result) if __name__ == '__main__': loop = asyncio.get_event_loop() main() # 输出 ''' Hello World:1637152588.856801 Hello World:1637152588.856801 Hello World:1637152588.8577979 Hello World:1637152588.8619971 Hello World:1637152588.864993 [b'<!DOCTYPE HTML PUBLIC "- ... '''
- 高层级 API 代码,省去了显式的定义事件循环等,看上去更加简洁、容易理解:
import time import asyncio from aiohttp import ClientSession tasks = [] url = "https://www.baidu.com/{}" async def hello(url): async with ClientSession() as session: async with session.get(url) as response: print('Hello World:%s' % time.time()) return await response.read() async def main(): hello_list = [hello(url.format(i)) for i in range(5)] P = await asyncio.gather(*hello_list) print(P) if __name__ == '__main__': asyncio.run(main()) # 输出 ''' Hello World:1637152648.7986703 Hello World:1637152648.8146682 Hello World:1637152648.8151577 Hello World:1637152648.8450766 Hello World:1637152648.8491611 [b'<!DOCTYPE HTML PUBLIC "-/... '''
asynico 还处于不断完善阶段,版本升级后都会有些小的变动,请及时查阅文档:asynico官方文档