在异步循环关闭之前,如何等待对象的__del__完成?

问题描述 投票:0回答:1

我有一个将在其中包含aiohttp.ClientSession对象的类。

通常在你使用时

async with aiohttp.ClientSession() as session:  
   # some code

会话将在调用会话的__aexit__方法后关闭。

我不能使用上下文管理器,因为我希望在对象的整个生命周期内保持会话持久。

这有效:

import asyncio
import aiohttp

class MyAPI:
    def __init__(self):
        self.session = aiohttp.ClientSession()

    def __del__(self):
        # Close connection when this object is destroyed
        print('In __del__ now')
        asyncio.shield(self.session.__aexit__(None, None, None))



async def main():
    api = MyAPI()

asyncio.run(main())

但是,如果在某个地方引发异常,则在__aexit__方法完成之前关闭事件循环。我怎么能克服这个?

堆栈跟踪:

Traceback (most recent call last):
  File "/home/ron/.PyCharm2018.3/config/scratches/async.py", line 19, in <module>
    asyncio.run(main())
  File "/usr/local/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/local/lib/python3.7/asyncio/base_events.py", line 568, in run_until_complete
    return future.result()
  File "/home/ron/.PyCharm2018.3/config/scratches/async.py", line 17, in main
    raise ValueError
ValueError
In __del__ now
Exception ignored in: <function MyAPI.__del__ at 0x7f49982c0e18>
Traceback (most recent call last):
  File "/home/ron/.PyCharm2018.3/config/scratches/async.py", line 11, in __del__
  File "/usr/local/lib/python3.7/asyncio/tasks.py", line 765, in shield
  File "/usr/local/lib/python3.7/asyncio/tasks.py", line 576, in ensure_future
  File "/usr/local/lib/python3.7/asyncio/events.py", line 644, in get_event_loop
RuntimeError: There is no current event loop in thread 'MainThread'.
sys:1: RuntimeWarning: coroutine 'ClientSession.__aexit__' was never awaited
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7f49982c2e10>
python python-asyncio aiohttp contextmanager
1个回答
5
投票

不要使用__del__钩子来清理异步资源。您根本无法计算它被调用,更不用说控制何时使用它或者当时异步循环仍然可用。你真的想明确处理这个问题。

使用finally处理程序,使API成为异步上下文管理器,或以其他方式显式清除退出时的资源; withasync with语句基本上是为了封装传统上在finally块中处理的资源清理。

我在这里将API实例作为上下文管理器:

class MyAPI:
    def __init__(self):
        self.session = aiohttp.ClientSession()

    async def __aenter__(self):
        return self

    async def __aexit__(self, *excinfo):
        await self.session.close()

请注意,ClientSession.__aexit__()所做的一切都是等待self.close(),所以上面的内容直接指向那个协同程序。

然后在主循环中使用它:

async def main():
    async with MyAPI() as api:
        pass

另一种选择是将自己的会话对象提供给MyAPI实例,并在完成后自行承担关闭它的责任:

class MyAPI:
    def __init__(self, session):
        self.session = session

async def main():
    session = aiohttp.ClientSession()
    try:
        api = MyAPI(session)
        # do things with the API
    finally:
        await session.close()
© www.soinside.com 2019 - 2024. All rights reserved.