Python HTTPX|运行时错误:连接池已关闭,但仍有6个HTTP请求/响应在进行中

hivapdat  于 2022-12-21  发布在  Python
关注(0)|答案(2)|浏览(194)

我在使用HTTPX模块时多次遇到这个错误,我相信我知道它的意思,但我不知道如何解决它。
在下面的例子中,我有一个异步函数gather_player(),它向我正在使用的API发送get请求,然后返回指定NBA球队的所有球员的列表,在teamRoster()中,我使用asyncio.run()来初始化gather_player(),这就是产生这个错误的代码行:第一个月

async def gather_players(list_of_urlCodes):

    async def get_json(client, link):
        response = await client.get(BASE_URL + link)

        return response.json()['league']['standard']['players']

    async with httpx.AsyncClient() as client:

        tasks = []
        for code in list_of_urlCodes:
            link = f'/prod/v1/2022/teams/{code}/roster.json'
            tasks.append(asyncio.create_task(get_json(client, link)))
        
        list_of_people = await asyncio.gather(*tasks)
        
        return list_of_people

def teamRoster(list_of_urlCodes: list) -> list:
        list_of_personIds = asyncio.run(gather_players(list_of_urlCodes))

        finalResult = []
        for person in list_of_personIds:
            personId = person['personId']

            #listOfPLayers is a list of every NBA player that I got 
            #from a previous get request
            for player in listOfPlayers:
                if personId == player['personId']:
                    finalResult.append({
                        "playerName": f"{player['firstName']} {player['lastName']}",
                        "personId": player['personId'],
                        "jersey": player['jersey'],
                        "pos": player['pos'],
                        "heightMeters": player['heightMeters'],
                        "weightKilograms": player['weightKilograms'],
                        "dateOfBirthUTC": player['dateOfBirthUTC'],
                        "nbaDebutYear": player['nbaDebutYear'],
                        "country": player['country']
                    })

        return finalResult
  • 注意:我的原始脚本中的teamRoster()函数实际上是一个类方法,我在脚本的前面部分也使用了相同的技术来使用异步函数发送多个get请求。
mv1qrgav

mv1qrgav1#

我终于找到了这个问题的解决方案。由于某种原因,上下文管理器:async with httpx.AsyncClient() as client无法正确关闭AsyncClient。解决此问题的快速方法是使用以下命令手动关闭它:client.aclose()
之前:

async with httpx.AsyncClient() as client:

    tasks = []
    for code in list_of_urlCodes:
        link = f'/prod/v1/2022/teams/{code}/roster.json'
        tasks.append(asyncio.create_task(get_json(client, link)))

    list_of_people = await asyncio.gather(*tasks)

    return list_of_people

之后:

client = httpx.AsyncClient()

tasks = []
for code in list_of_urlCodes:
    link = f'/prod/v1/2022/teams/{code}/roster.json'
    tasks.append(asyncio.create_task(get_json(client, link)))

list_of_people = await asyncio.gather(*tasks)
client.aclose()    

return list_of_people
6kkfgxo0

6kkfgxo02#

公认的答案是原始代码无法正确关闭客户端,因为它没有调用aclose(),虽然从技术上讲这是正确的,但异步上下文管理器退出方法(__aexit__)的实现本质上是aclose()实现的重复。
实际上,您可以看出连接 * 已 * 关闭,因为错误消息抱怨在连接关闭后仍有6个HTTP请求未完成。
相比之下,接受的答案通过显式地“不”关闭连接来“修复”错误。因为httpx.AsyncClient.aclose是异步函数,所以在不使用await ing的情况下调用它会创建一个协程,该协程实际上并没有被调度在事件循环上执行。然后,当函数在没有实际执行的情况下立即返回时,该协程将被销毁。Python * 应该 * 打印一个RuntimeWarning,而client.aclose()从来没有被等待过。因此,在进程终止和强制关闭每个连接之前,每个请求都有足够的时间来完成,这样RuntimeError就永远不会被引发。
虽然我不知道一些请求仍然在执行的全部原因,但我怀疑是在函数返回和连接关闭之前没有完成的结尾处的一些清理。则错误将很可能消失,因为客户端将有时间在其每个请求之后完成和清除。(请注意,我并不是说这是一个很好的解决方案,而是说这有助于提供证据来支持我的解释。)
不要使用asyncio.gather,尝试使用Python文档中推荐的asyncio.gather,这样你的新代码看起来就像这样:

async def gather_players(list_of_urlCodes):

    async def get_json(client, link):
        response = await client.get(BASE_URL + link)

        return response.json()['league']['standard']['players']

    async with httpx.AsyncClient() as client:

        async with asyncio.TaskGroup() as tg:
            tasks = [tg.create_task(get_json(client, f'/prod/v1/2022/teams/{code}/roster.json')) for code in list_of_urlCodes]

        list_of_people = [task.result for task in tasks]
        
    return list_of_people

这显然不是生产级代码,因为它缺少错误处理,但它足够清楚地演示了该建议。

相关问题