python协程库asyncio的基本用法

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库时有以下报错,所以在上述代码中还是使用了手工创建事件循环的方法来执行,但是在其它场景模块的使用没有该问题。

微信截图_20230410203321.png

另外使用了协程以后这个请求运行时间大大提高了,下图就是使用前的耗时

微信截图_20230410203729.png

使用协程后耗时如下

微信截图_20230410203717.png

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://sulao.cn/post/915.html

我要评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。