问题描述:
s3程序是一个测试tornado web人脸检测功能的测试函数。在concurrent_reaquest的图片数量较大时会产生报错。
async def request_images(images):
async with aiohttp.ClientSession(json_serialize=ujson.dumps) as session:
url = "http://localhost:9999/images"
payload = {'instances': images}
async with session.post(url, json=payload) as resp:
v= await resp.text()
print(v)
async def main():
...
concurrent_requests = (request_images(re_im2) for i in range(10000))
await asyncio.gather(*concurrent_requests)
...
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
报错信息:
Traceback (most recent call last):
File "C:/Users/14192/MTCNN-zzk/MTCNN/s3aiosend_test.py", line 58, in
loop.run_until_complete(main())
File "C:\Users\14192\Anaconda3\lib\asyncio\base_events.py", line 454, in run_until_complete
self.run_forever()
File "C:\Users\14192\Anaconda3\lib\asyncio\base_events.py", line 421, in run_forever
self._run_once()
File "C:\Users\14192\Anaconda3\lib\asyncio\base_events.py", line 1389, in _run_once
event_list = self._selector.select(timeout)
File "C:\Users\14192\Anaconda3\lib\selectors.py", line 323, in select
r, w, _ = self._select(self._readers, self._writers, [], timeout)
File "C:\Users\14192\Anaconda3\lib\selectors.py", line 314, in _select
r, w, x = select.select(r, w, w, timeout)
ValueError: too many file descriptors in select()
代码分析
从代码来看,异步方法main中,concurrent_requests是一个列表生成器,里面的每个对象都是一次tornado的服务调用返回结果。列表生成器被函数asyncio.gather调用,返回一个Awaitable对象。这里的await关键字是配合函数async关键字使用的,表示挂起自身的协程,并等待另一个协程完成直到返回结果。
main函数被定义成一个协程对象,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。因此,在代码的最后使用了loop = asyncio.get_event_loop() loop.run_until_complete(main())
这样两行代码,启动了一个事件循环,并将main函数这个协程注册到事件循环中,启动事件循环。
报错信息也来自事件循环的run_until_complete方法。
可以看到只有被注册到事件循环loop中的async main方法会被当成协程task来处理。而代码中将main方法整个看成一个协程,这个协程内部的代码出现了await,也就是当这个函数出现阻塞的时候,这个协程就会让出控制权,以便loop调用其他的协程。而我们希望实现的是在main函数内部,面对多个tornado web查询时,可以将多个tornado查询看作是不同的协程。而不是把main看成一个协程,在main中遇到await把main程序给挂起,因为这里只有一个协程,因此这个不是主要问题,只是需要美化的部分。
代码主要问题在于asyncio内部使用了select方法,select方法对打开的文件字符长度有最大的限制。一次性将太多的需要web调用函数作为任务时,会导致List过大,报错。并发的思想没有错,这里使用回调接口会比较合适。
解决思路
我们希望的是,对于tornado的服务调用是并行进行的,使用回调接口的实现并发的tornado服务访问。
因此首先将对tornado的访问函数写成支持回调的方式
async def request_images(images):
async with aiohttp.ClientSession(json_serialize=ujson.dumps) as session:
url = "http://localhost:9999/images"
payload = {'instances': images}
async with session.post(url, json=payload) as resp:
return await resp.text()
def post_deal(content):
print(content)
将loop事件循环放入main函数中,实现对每次访问tornado服务的异步调用。
def main():
face_img = cv2.imread(im_name)
re_im = face_img
re_im2 = load_image(im_name)
import time
start = time.time()
loop = asyncio.get_event_loop()
# concurrent_requests = [asyncio.ensure_future(request_images(re_im2)) for i in range(10000)]
for i in range(10000):
concurrent_request = asyncio.ensure_future(request_images(re_im2))
concurrent_request.add_done_callback(post_deal)
loop.run_until_complete(concurrent_request)
end = time.time() - start
print(end)
if __name__ == '__main__':
main()
这样代码的问题就通过异步回调解决了。
参考文章
python的重要模块-asynic
asyncio 异步爬虫