asyncio库是python实现异步任务的库,在python3.4引入,在3.5和3.7分别都有一些改进和用法的改变
协程本质就是一种用户态的上下文切换技术,我们创建事件循环,然后将我们的任务丢入到这个事件循环中就实现了任务的异步IO
本地环境python3.9,所以例子可能无法在python3.8以下环境跑通,咱们也不再回头学习低版本python中的写法了。
首先我们要了解几个关键字的含义
async:用来声明一个函数为异步函数,异步函数的特点是函数能在执行的过程中挂起,去执行其他的异步函数,等到挂起条件消失后,再回来执行后续代码。
await:await会把当前协程任务挂起,并把任务控制权交给事件循环,事件循环相当于指挥者角色,指挥下一个协程任务执行。await 用来声明程序挂起,比如异步函数执行到某一步时需要等待的时间很长,就在此时将此异步函数挂起,去执行其他的异步函数。await 后面只能跟可等待对象。
create_task方法或ensure_future方法并不会把协程任务添加进事件循环,而是将其排入日程准备执行,因此会出现协程任务未执行完全的现象;在单任务中,run函数会把日程上的协程任务添加进事件循环;在多任务中,wait协程函数或gather函数收集日程上的协程任务,并添加进事件循环。
目前比较简单的异步IO的例子,由于requests是同步库,所以这里使用了aiohttp异步库,网上有些教程不太严谨。
使用ensure_future方法
from aiohttp import ClientSession import asyncio import time async def get_http_status(url): async with ClientSession() as session: async with session.get(url) as response: print("当前URL: {}, 状态: {}".format(url, response.status)) async def main(): tasks = [] for p in range(1, 30): url = "https://sulao.cn/page_{}.html".format(p) tasks.append(asyncio.ensure_future(get_http_status(url))) #可以直接将get_http_status(url)函数不做任何转换添加tasks列表,但是该方式在python3.11里面将被移除 await asyncio.wait(tasks) if __name__ == "__main__": start_time = time.time() #asyncio.run(main()) loop = asyncio.get_event_loop() loop.run_until_complete(main()) cost_time = time.time() - start_time print("Cost time {:.2f}s".format(cost_time))
使用create_task方法,在python3.7以后版本协程任务手动创建主要通过create_task方法,但是ensure_future方法仍然可以用,用法上基本一致。
from aiohttp import ClientSession import asyncio import time async def get_http_status(url): async with ClientSession() as session: async with session.get(url) as response: print("当前URL: {}, 状态: {}".format(url, response.status)) async def main(): tasks = [] for p in range(1, 30): url = "https://sulao.cn/page_{}.html".format(p) tasks.append(asyncio.create_task(get_http_status(url))) #可以直接将get_http_status(url)函数不做任何转换添加tasks列表,但是该方式在python3.11里面将被移除 await asyncio.wait(tasks) if __name__ == "__main__": start_time = time.time() #asyncio.run(main()) loop = asyncio.get_event_loop() loop.run_until_complete(main()) cost_time = time.time() - start_time print("Cost time {:.2f}s".format(cost_time))
gather方法
gather方法和wait方法的区别
1.wait返回两个集合,done和pending,done为已完成的协程任务集合,pending为超时未完成的协程任务集合,需通过task.result()获取每个协程任务返回的结果;而gather返回一个列表,包含所有已完成的协程任务的结果,不需要再进行调用或其他操作就可以得到全部结果。
2.gather的返回结果顺序与协程任务传入事件循环时一致,wait的返回结果是无序的。
3.gather具有把普通协程对象包装成协程任务的能力,wait没有。wait只能接收协程任务列表做参数。
from aiohttp import ClientSession import asyncio import time async def get_http_status(url): async with ClientSession() as session: async with session.get(url) as response: print("当前URL: {}, 状态: {}".format(url, response.status)) async def main(): tasks = [] for p in range(1, 30): url = "https://sulao.cn/page_{}.html".format(p) tasks.append(get_http_status(url)) await asyncio.gather(*tasks) if __name__ == "__main__": start_time = time.time() #asyncio.run(main()) loop = asyncio.get_event_loop() loop.run_until_complete(main()) cost_time = time.time() - start_time print("Cost time {:.2f}s".format(cost_time))
另外在python3.7以上不需要手动创建事件循环了,但是我使用aiohttp异步http库时有以下报错,所以在上述代码中还是使用了手工创建事件循环的方法来执行,但是在其它场景模块的使用没有该问题。
另外使用了协程以后这个请求运行时间大大提高了,下图就是使用前的耗时
使用协程后耗时如下