python异步IO--asyncio返回有序结果的异步请求

1 篇文章 0 订阅
1 篇文章 0 订阅

啥是异步IO?

Python3 异步IO–asyncio
asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。
asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

上面的不讲人话,下面是翻译

假设一个url支持同时处理1W的请求,处理时间是1秒,我们要请求这个url 1000次。
如果是依次请求,全部处理完需要花费1000秒。
asyncio就不一样了,他可以在一瞬间将1000个请求同时发出,然后等待所有返回结果。
这样完成1000次请求只需要1秒钟

发现的问题

在网上找了很多demo后,效率是提高了,但是又出了新问题,1000个请求发出后,系统并不能保证哪个请求先返回,所以返回的结果是乱序的,在返回结果里无标识的情况下,根本无法确定哪个结果对应哪个请求。

解决方案

我们通过百度识图的API试验下,举个例子,
(百度识图免费的,某些接口并发量是2,刚好测试)

我们测试识别几张名片
(图片在git上)
名片1
名片1
名片2
名片2
名片3
名片3
名片4
名片4

直接上代码,改良后的demo

class BaiduOCR(object):
    """
    基于百度识别
    示例Demo
    """

    def __init__(self, sem):
        self.__taskList = []  # 存放loop管理的task
        self.__sem = sem  # 并发数量上限

    def ocr_res(self, req_data, url):
        """
        返回所有请求的识别结果
        :param req_data:
        :param url:
        :return:
        """
        self.mark_res_async(req_data, url)
        result = [json.loads(t.result()) for t in self.__taskList]
        print("result", result)

    def mark_res_async(self, req_data, url):
        """
        配置loop管理所有请求
        :param req_data:
        :param url:
        :return:
        """
        asyncio.set_event_loop(asyncio.new_event_loop())  # 支持多线程的async
        sem = asyncio.Semaphore(self.__sem)  # 控制并发数量
        for d in req_data:
            # 每个task就是一个请求
            # 将_asyncio.Task对象实例的引用暂存起来
            self.__taskList.append(asyncio.ensure_future(self.req_baidu_api(d, sem, url)))
        loop = asyncio.get_event_loop()
        print("__taskList2", self.__taskList)
        tasks = set(self.__taskList)
        loop.run_until_complete(asyncio.wait(tasks))  # 等待所有请求完成
        loop.close()

    async def req_baidu_api(self, data, sem, url):
        headers = {}  # 请求头
        res = None
        try:
            timeout = aiohttp.ClientTimeout(total=30)
            async with sem:
                async with aiohttp.ClientSession(timeout=timeout) as session:
                    async with session.post(url=url, data=data, headers=headers) as resp:
                        res = await resp.text()
                        return res
        except Exception as e:
            return res
[{'words_result_num': 10, 'words_result': {'PC': [''], 'TEL': ['0572-86888888', '0572-866666', '88866666'], 'TITLE': ['经理'], 'EMAIL': ['88888@163.com'], 'FAX': [''], 'URL': ['www.88866.com'], 'ADDR': ['江苏省苏州市张家港市中港镇小区'], 'COMPANY': ['设计创意有限公司'], 'MOBILE': ['13813813813'], 'NAME': ['']}, 'log_id': 1427092559734222417}, 

{'words_result_num': 10, 'words_result': {'PC': [''], 'TEL': ['+8675782713411'], 'TITLE': [''], 'EMAIL': [''], 'FAX': [''], 'URL': ['www.ksw1506.com'], 'ADDR': [''], 'COMPANY': [''], 'MOBILE': [''], 'NAME': ['全国']}, 'log_id': 1427092561799509140}, 

{'words_result_num': 10, 'words_result': {'PC': [''], 'TEL': ['0757-88331625'], 'TITLE': [''], 'EMAIL': ['lj-ifeng@osugroup.com'], 'FAX': [''], 'URL': [''], 'ADDR': ['广东佛山市祖庙路33号百花广场45楼'], 'COMPANY': [''], 'MOBILE': ['13925949103'], 'NAME': ['']}, 'log_id': 1427092565150553584}, 

{'words_result_num': 10, 'words_result': {'PC': [''], 'TEL': ['8888888'], 'TITLE': [''], 'EMAIL': [''], 'FAX': ['8800000'], 'URL': [''], 'ADDR': ['惠东县华侨大道'], 'COMPANY': ['COMPARY有限公司'], 'MOBILE': ['1511000'], 'NAME': ['']}, 'log_id': 1427092620186761321}]

这就是按顺序输出的结果了

改进的点和原因分析

请求完成前

__taskList bef [<Task pending coro=<BaiduOCR.req_baidu_api() running at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149>>, 
<Task pending coro=<BaiduOCR.req_baidu_api() running at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149>>, 
<Task pending coro=<BaiduOCR.req_baidu_api() running at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149>>, 
<Task pending coro=<BaiduOCR.req_baidu_api() running at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149>>]

请求完成后

__taskList aft [<Task finished coro=<BaiduOCR.req_baidu_api() done, defined at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149> result='{"words_resu...559734222417}'>, 
<Task finished coro=<BaiduOCR.req_baidu_api() done, defined at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149> result='{"words_resu...561799509140}'>, 
<Task finished coro=<BaiduOCR.req_baidu_api() done, defined at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149> result='{"words_resu...565150553584}'>, 
<Task finished coro=<BaiduOCR.req_baidu_api() done, defined at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149> result='{"words_resu...620186761321}'>]

发现了奇怪的东西
Task对象实际上就是一个请求,有pending 和finished两种状态,结束后,我们调用Task的resule方法获取返回结果。
loop也是对Task进行管理,保证所有请求返回结果后,再关闭loop。
而且syncio.wait()的都是Task对象的引用。
那么这里都可以操作一波了
我们提前将Task对象的引用按照自己喜欢的顺序保存下来,保存到一个列表里(self.__taskList),等待所有请求完成后,loop关闭,我们直接通过遍历__taskList获取Task对象,调用result方法得到请求结果,就是我们想要的顺序了。
BTW:__taskList也可以换成dict,键为请求名,值为请求结果,随便怎么搞,只要能保证一一对应就可以

完整代码在git,需要的的自行拉取
https://github.com/Guristasrabbit/baiduOCR_asyncio_demo.git

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值