python asycio RuntimeWarning:从未等待协程

时间:2020-06-19 18:28:09

标签: python-3.x python-asyncio aiohttp

我正在尝试同时向网址(〜50)发送许多请求。

from asyncio import Queue

import yaml
import asyncio

from aiohttp import ClientSession, TCPConnector


async def http_get(url, cookie):
    cookie = cookie.split('; ')
    cookie1 = cookie[0].split('=')
    cookie2 = cookie[1].split('=')
    cookies = {
        cookie1[0]: cookie1[1],
        cookie2[0]: cookie2[1]
    }

    async with ClientSession(cookies=cookies) as session:
        async with session.get(url, ssl=False) as response:
            return await response.json()


class FetchUtil:
    def __init__(self):
        self.config = yaml.safe_load(open('../config.yaml'))

    def fetch(self):
        asyncio.run(self.extract_objects())

    async def http_get_objects(self, object_type, limit, offset):
        path = '/path' + \
               '?query=&filter=%s&limit=%s&offset=%s' % (
                   object_type,
                   limit,
                   offset)
        return await self.http_get_domain(path)

    async def http_get_objects_limit(self, object_type, offset):
        result = await self.http_get_objects(
            object_type,
            self.config['object_limit'],
            offset
        )
        return result['result']

    async def http_get_domain(self, path):
        return await http_get(
            f'https://{self.config["domain"]}{path}',
            self.config['cookie']
        )

    async def call_objects(self, object_type, offset):
        result = await self.http_get_objects_limit(
            object_type,
            offset
        )
        return result

    async def extract_objects(self):
        calls = []
        object_count = (await self.http_get_objects(
                'PV', '1', '0'))['result']['count']
        for i in range(0, object_count, self.config['object_limit']):
            calls.append(self.call_objects('PV', str(i)))

        queue = Queue()
        for i in range(0, len(calls), self.config['call_limit']):
            results = await asyncio.gather(*calls[i:self.config['call_limit']])
            await queue.put(results)

使用fetch作为入口点运行此代码后,我收到以下错误消息:

/usr/local/Cellar/python/3.7.4_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/events.py:88: RuntimeWarning: coroutine 'FetchUtil.call_objects' was never awaited
      self._context.run(self._callback, *self._args)
    RuntimeWarning: Enable tracemalloc to get the object allocation traceback

asyncio.gather之后停止执行的程序是第一次返回。因为我以为自己努力确保所有功能都是异步任务,所以我很难理解此消息。我没有await的唯一功能是call_objects,因为我希望它可以同时运行。

https://xinhuang.github.io/posts/2017-07-31-common-mistakes-using-python3-asyncio.html#org630d301

本文中的解释如下:

在许多情况下都可能发生此运行时警告,但原因是 相同:通过调用异步来创建协程对象 函数,但永远不会插入到EventLoop中。

我相信这就是我用asyncio.gather调用异步任务时正在做的事情。

我应该注意,当我在http_get中放入print('url')时,它会像我想要的那样输出前50个网址,当asyncio.gather第一次返回时,似乎会出现问题。

1 个答案:

答案 0 :(得分:0)

发布的代码存在逻辑错误:[i:self.config['call_limit']]应该为[i:i + self.config['call_limit']]

它会导致错误,因为该表达式在第一个表达式之后的迭代中求值到一个入口切片,从而导致某些调用协程永远不会传递给gather,因此从未等待)

我实际上不明白为什么它不只是多次执行相同的请求而不是由于错误而停止。

因为您的i将递增,因此在除第一次循环之外的每个循环迭代中,它都比call_limit大。例如,假设call_limit为10,则i在第一次迭代中将为0,到目前为止,您将等待calls[0:10]。但是在下一次迭代中,i将为10,您将等待calls[10:10],这是一个空切片。在此之后的迭代中,您将等待calls[20:10](也是一个空片,尽管从逻辑上讲应该是一个错误),然后等待calls[30:10],然后再次为空,依此类推。只有第一次迭代才能获取列表的实际成员。