【问题标题】:RuntimeError when running coroutine from __init__从 __init__ 运行协程时出现 RuntimeError
【发布时间】:2019-08-18 16:28:57
【问题描述】:

这是一个示例代码。

class Foo:
    def __init__(self):
        self._run_coro()

    def _run_coro(self):
        async def init():
            bar = #some I/O op
            self.bar = bar
        loop = asyncio.get_event_loop()
        loop.run_until_complete(init())

    async def spam(self):
        return await #I/O op

async def main():
    foo = Foo()
    await foo.spam()

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

当我运行这段代码时,我得到以下异常: RuntimeError: This event loop is already running

如果我在main 之外初始化Foo,代码将毫无例外地运行。我想初始化Foo,以便在初始化期间运行一个协程,该协程创建一个类属性bar

我无法弄清楚如何正确地做到这一点。如何从__init__ 运行协程。

任何帮助将不胜感激。

class Foo:
     def __init__(self):
         self.session = requests.Session()
         self.async_session = None
         #I guess this can be done to initialize it. 
         s = self.init_async_session()
         try:
             s.send(None)
         except StopIteration:
             pass
         finally:
             s.close()

     async def init_async_session(self):
         #ClientSession should be created inside a coroutine. 
         self.async_session = aiohttp.ClientSession()

初始化self.async_session的正确方法是什么

【问题讨论】:

    标签: python-3.x python-asyncio


    【解决方案1】:

    如果某些方法使用异步的东西,它也应该明确定义为异步的。这是asyncio 背后的核心理念:让您编写代码时始终知道某个任意方法是否可以异步执行某些操作。

    在您的 sn-p 中,您想要在同步方法 __init__asyncio 中执行异步操作 (bar I/O) 禁止它。您应该使_run_coro异步并异步初始化Foo,例如using__await__方法:

    import asyncio
    
    
    class Foo:
        def __await__(self):
            return self._run_coro().__await__()
    
        async def _run_coro(self):  # real async initializer
            async def init():
                await asyncio.sleep(1)  # bar I/O
                self.bar = 123
            await init()
            return self
    
        async def spam(self):
            return await asyncio.sleep(1)  # I/O op
    
    
    async def main():
        foo = await Foo()
        await foo.spam()
    
    
    asyncio.run(main())  # instead of two lines in Python 3.7+
    

    您可能有兴趣阅读this answer 以更好地了解asyncio 的工作原理以及如何处理它。

    更新:

    s = self.init_async_session()
    try:
        s.send(None)
    

    不要做这样的事情:生成器的方法只是协程的实现细节。您可以预测协程对调用 .send() 方法的反应,并且可以依赖此行为。

    如果你想执行协程使用await,如果你想“在后台”启动它使用taskasyncio的其他函数doc

    初始化self.async_session的正确方法是什么

    当涉及到aiohttp.ClientSession 时,它不仅应该被创建,还应该被正确关闭。最好的方法是使用异步上下文管理器,如 aiohttp doc 所示。

    如果您想在Foo 中隐藏此操作,您也可以将其设为异步管理器。完整示例:

    import aiohttp
    
    
    class Foo:
        async def __aenter__(self):
            self._session = aiohttp.ClientSession()
            await self._session.__aenter__()
            return self
    
        async def __aexit__(self, *args):
            await self._session.__aexit__(*args)
    
        async def spam(self):
            url = 'http://httpbin.org/delay/1'
            resp = await self._session.get(url)
            text = await resp.text()
            print(text)
    
    
    async def main():
        async with Foo() as foo:
            await foo.spam()
    
    
    asyncio.run(main())
    

    更新2:

    您可以组合从上方初始化/关闭对象的方法以获得您喜欢的结果。只要您记住这两个操作都是异步的,因此应该等待,一切都应该没问题。

    另一种可能的方式:

    import asyncio
    import aiohttp
    
    
    class Foo:
        def __await__(self):
            return self._init().__await__()
    
        async def _init(self):
            self._session = aiohttp.ClientSession()
            await self._session.__aenter__()
            return self
    
        async def close(self):
            await self._session.__aexit__(None, None, None)
    
        async def spam(self):
            url = 'http://httpbin.org/delay/1'
            resp = await self._session.get(url)
            text = await resp.text()
            print(text)
    
    
    async def main():
        foo = await Foo()
        try:
            await foo.spam()
        finally:
            await foo.close()
    
    
    asyncio.run(main())
    

    【讨论】:

    • 谢谢迈克尔! Foo 类同时包含同步和异步方法。我必须有一个__init__ 方法来设置实例变量,并且其中一个实例变量将通过执行协程来初始化。
    • 我已经用另一个例子更新了我原来的问题。
    • 我想创建一个self.async_session 属性的原因是我想持久化它ClienSession。我知道ClientSession 必须关闭,我有一个清理方法可以做到这一点。此外,Foo 就像一个基类,为调用者提供同步(resquests.Session)和异步(aiohtto.ClientSession)方法。
    • @user6037143 我更新了答案。您可以将包装器添加到 Foo 以添加在事件循环内运行每个异步方法的同步接口。可以看例子here
    • 我想出了一个解决方案并将其发布为答案。
    【解决方案2】:

    这是我的解决方案。

    class Session:
        def __init__(self, headers):
            self._headers = headers
            self._session = requests.Session()
            self._async_session = None
    
        async def _init(self):
            self._session = aiohttp.ClientSession(headers=headers)
    
        async def async_request(self, url):
           while True:
                try:
                    async with self._async_session.get(url) as resp:
                        resp.raise_for_status()
                        return await resp.text()
                except aiohttp.client_exceptions.ClientError:
                     #retry or raise
                except AttributeError:
                    if isinstance(self._async_session, aiohttp.ClientSession):
                        raise
                    await self._init()
    
        def request(self, url):
            return self._session.get(url).text
    
        async def close(self):
            if isinstance(self._async_session, aiohttp.ClientSession):
                await self._session.close()
    
    async def main():
        session = Session({})
        print(await session.async_request('https://httpstat.us/200')
        await session.close()
    
    asyncio.run(main())
    

    我可以初始化Session 类并发出同步和异步请求。我不必显式调用await session._init() 来初始化self._async_session,因为当调用session._async_request 并且self._async_session 为None 时,将调用await session._init() 并重试请求。

    【讨论】:

      猜你喜欢
      • 2020-08-15
      • 1970-01-01
      • 2020-08-12
      • 2021-06-01
      • 2012-01-11
      • 1970-01-01
      • 1970-01-01
      • 2020-09-06
      • 1970-01-01
      相关资源
      最近更新 更多