<p>虽然这是一个老问题,但我可能有一些令人信服的东西要分享,因为我有一个类似的情况。按照@Isabi的观点(回答2020-12-28),您需要使用事件循环将客户机与您的操作分离,然后手动控制其生命周期</p>
<p>在我的例子中,我需要对客户机进行更多的控制,这样我就可以将请求与发送分开,并在关闭客户机时利用会话池等。下面提供的示例演示了如何使用http.AsyncClient作为类成员,并在退出时关闭它</p>
<p>在解决这个问题的过程中,我遇到了一个异步学习曲线,但很快发现它是。。。其实不算太坏。它不像Go[lang]那样干净,但在摆弄它一两个小时后就开始有意义了。完全披露:我仍然怀疑这是否100%正确</p>
<p>关键部分在<code>__init__</code>、<code>close</code>和<code>__del__</code>方法中。对我来说,还有待回答的是,在上下文管理器中使用http.AsyncClient是否真的会重置连接,等等。我只能假设它会重置连接,因为这对我来说是有意义的。我不禁要问:这有必要吗</p>
<pre><code>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)])
</code></pre>
<p>由于我最近一直在使用Go,我最初编写了一些带有闭包的方法,因为它们似乎有意义;最后,通过将闭包转换为类方法,我能够(IMHO)在分离/封装/隔离之间提供良好的平衡</p>
<p>由此产生的使用界面感觉平易近人且易于阅读——我看到自己正在编写基于类的异步</p>