繁体   English   中英

Python异步功能中的多次等待

[英]Multiple Await in Python Async Function

我正在自定义类中使用aiohttp会话以及信号量:

async def get_url(self, url):

    async with self.semaphore:
        async with self.session.get(url) as response:
            try:
                text_response = await response.text()
                read_response = await response.read()
                json_response = await response.json()
                await asyncio.sleep(random.uniform(0.1, 0.5))
            except aiohttp.client_exceptions.ContentTypeError:
                json_response = {}

            return {
                'json': json_response,
                'text': text_response,
                'read': read_response,
                'status': response.status,
                'url': response.url,
            }

我有两个问题:

  1. 在一个异步函数中包含多个await语句是否正确/不正确? 我需要同时返回response.text()和response.read()。 但是,根据URL的不同,response.json()可能可用或可能不可用,因此我将所有内容都放入try / except块中以捕获此异常。

  2. 由于我正在使用此功能遍历不同的RESTful API端点列表,因此我通过信号量(同时设置为最大值100)来控制同时请求的数量,但是我还需要错开请求,以免它们被日志阻塞主机。 因此,我认为我可以通过添加一个在0.1-0.5秒之间随机选择的asyncio.sleep来完成此操作。 这是在两次请求之间执行小的等待的最佳方法吗? 我应该将其移动到函数的开头而不是结尾吗?

  1. 就您所知道的等待而言,在一个异步函数中具有多个等待是绝对好的,就像非常正常的顺序执行一样,每个等待都是一个接一个的等待。 关于aiohttp的一件事是,最好先调用read()并捕获UnicodeDecodeError ,因为内部的text()json()调用read()并处理其结果,所以您不希望该处理防止至少返回read_response 您不必担心read()会被多次调用,它只是在第一次调用时被缓存在响应实例中。

  2. 随机交错是突发流量的一种简单有效的解决方案。 但是,如果您想精确控制任意两个请求之间的最小时间间隔-出于学术原因,您可以设置两个信号灯:

     def __init__(self): # something else self.starter = asyncio.Semaphore(0) self.ender = asyncio.Semaphore(30) 

    然后更改get_url()以使用它们:

     async def get_url(self, url): await self.starter.acquire() try: async with self.session.get(url) as response: # your code finally: self.ender.release() 

    由于starter初始化为零,因此所有get_url()协程将在starter阻塞。 我们将使用单独的协程对其进行控制:

     async def controller(self): last = 0 while self.running: await self.ender.acquire() sleep = 0.5 - (self.loop.time() - last) # at most 2 requests per second if sleep > 0: await asyncio.sleep(sleep) last = self.loop.time() self.starter.release() 

    而且您的主程序应如下所示:

     def run(self): for url in [...]: self.loop.create_task(self.get_url(url)) self.loop.create_task(self.controller()) 

    因此,起初,控制器将在15秒内均匀释放starter 30次,因为这是ender的初始值。 在此之后,控制器将释放starter ,只要任何get_url()结束,如果0.5秒自上次发布过starter ,或者它会等待到那个时候。

    这里的一个问题是:如果要获取的URL不是内存中的恒定列表(例如,来自网络的持续不断的URL之间存在不可预测的延迟),则RPS限制器将失败(在实际上没有要获取的URL之前发布的启动程序为时过早)。 尽管这种情况下流量突发的可能性已经很小,但您仍需要进一步调整。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM