简体   繁体   English

如何使用 httpx.AsyncClient 作为 class 成员,并异步关闭

[英]how to use httpx.AsyncClient as class member, and close asynchronously

I want to use http client as a class member, but del function could not call await client.aclose() .我想使用 http 客户端作为 class 成员,但del function 无法调用 await client.aclose() eg:例如:

import httpx

class Foo(object):
    def __init__(self):
        self.client = httpx.AsyncClient()

    def __del__(self):
        await self.client.aclose()

refer:https://www.python-httpx.org/async/#opening-and-closing-clients how to safely aclose?参考:https://www.python-httpx.org/async/#opening-and-closing-clients如何安全关闭?

Although this is an older question, I may have something compelling to share as I had a similar situation.尽管这是一个较老的问题,但由于我遇到了类似的情况,所以我可能有一些令人信服的东西要分享。 To @Isabi's point (Answered 2020-12-28), you need to use an event loop to decouple the client from your operations and then manually control it's lifecycle.对于@Isabi 的观点(已回答 2020-12-28),您需要使用事件循环将客户端与您的操作分离,然后手动控制它的生命周期。

In my case, I need more control over the client such that I can separate the Request from the sending and when the client is closed so I can take advantage of session pooling, etc. The example provided below shows how to use http.AsyncClient as a class member and close it on exit.在我的例子中,我需要对客户端进行更多控制,以便我可以将请求与发送分开,以及何时关闭客户端,以便我可以利用 session 池等。下面提供的示例显示了如何使用 http.AsyncClient 作为class 成员并在退出时将其关闭。

In figuring this out, I bumped into an Asyncio learning curve but quickly discovered that it's... actually not too bad.在解决这个问题时,我遇到了 Asyncio 学习曲线,但很快发现它……实际上还不错。 It's not as clean as Go[lang] but it starts making sense after an hour or two of fiddling around with it.它不像 Go[lang] 那样干净,但在摆弄一两个小时后它开始变得有意义。 Full disclosure: I still question whether this is 100% correct.全面披露:我仍然质疑这是否 100% 正确。

The critical pieces are in the __init__ , close , and the __del__ methods.关键部分在__init__close__del__方法中。 What, to me, remains to be answered, is whether using a the http.AsyncClient in a context manager actually resets connections, etc. I can only assume it does because that's what makes sense to me.对我来说,还有待回答的是,在上下文管理器中使用 http.AsyncClient 是否真的会重置连接等。我只能假设它会重置,因为这对我来说很有意义。 I can't help but wonder: is this even necessary?我不禁想知道:这有必要吗?

import asyncio
import httpx
import time
from typing import Callable, List
from rich import print


class DadJokes:

    headers = dict(Accept='application/json')

    def __init__(self):
        """
        Since we want to reuse the client, we can't use a context manager that closes it.
        We need to use a loop to exert more control over when the client is closed.  
        """
        self.client = httpx.AsyncClient(headers=self.headers)
        self.loop = asyncio.get_event_loop()

    async def close(self):
        # httpx.AsyncClient.aclose must be awaited!
        await self.client.aclose()

    def __del__(self):
        """
        A destructor is provided to ensure that the client and the event loop are closed at exit.
        """
        # Use the loop to call async close, then stop/close loop.
        self.loop.run_until_complete(self.close())
        self.loop.close()

    async def _get(self, url: str, idx: int = None):
        start = time.time() 
        response = await self.client.get(url)
        print(response.json(), int((time.time() - start) * 1000), idx)

    def get(self, url: str):
        self.loop.run_until_complete(self._get(url))

    def get_many(self, urls: List[str]):
        start = time.time()
        group = asyncio.gather(*(self._get(url, idx=idx) for idx, url in enumerate(urls)))
        self.loop.run_until_complete(group)
        print("Runtime: ", int((time.time() - start) * 1000))


url = 'https://www.icanhazdadjoke.com'
dj = DadJokes()
dj.get_many([url for x in range(4)])

Since I've been using Go as of late, I originally wrote some of these methods with closures as they seemed to make sense;因为我最近一直在使用 Go,所以我最初写了一些带有闭包的方法,因为它们看起来很有意义; in the end I was able to (IMHO) provide a nice balance in between separation / encapsulation / isolation by converting the closures to class methods.最后,我能够(恕我直言)通过将闭包转换为 class 方法,在分离/封装/隔离之间提供一个很好的平衡。

The resulting usage interface feels approachable and easy to read - I see myself writing class based async moving forward.由此产生的使用界面感觉平易近人且易于阅读——我认为自己正在编写基于 class 的异步向前发展。

The problem might be due to the fact that client.aclose() returns an awaitable , which cannot be awaited in a normal def function.问题可能是由于client.aclose()返回一个awaitable ,它不能在正常的def function 中等待。

It could be worth giving a try with asyncio.run(self.client.aclose()) .尝试使用asyncio.run(self.client.aclose())可能是值得的。 Here it might occur an exception, complaining that you are using a different event loop (or the same, I don't know much of your context so I can't tell) from currently running one.在这里它可能会发生异常,抱怨您正在使用与当前正在运行的事件循环不同的event loop (或者相同,我不太了解您的上下文,所以我无法分辨)。 In this case you could get the currently running event loop and run the function from there.在这种情况下,您可以获得当前正在运行的event loop并从那里运行 function。

See https://docs.python.org/3/library/asyncio-eventloop.html for more information on how you could accomplish it.有关如何完成它的更多信息,请参阅https://docs.python.org/3/library/asyncio-eventloop.html

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

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