从__init__运行协程时出现RuntimeError

时间:2019-03-28 00:34:29

标签: python-3.x python-asyncio

这是示例代码。

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

如果我在Foo之外初始化main,则代码将毫无例外地运行。我想初始化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

的正确方法是什么

2 个答案:

答案 0 :(得分:2)

如果某些方法使用异步方法,则应将其明确定义为异步方法。这是asyncio背后的核心思想:让您以一种始终知道某种任意方法是否可以执行异步操作的方式编写代码。

在您的代码段中,您想在同步方法bar中执行异步操作(__init__ I / O),而asyncio则禁止这样做。您应该使_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())

答案 1 :(得分:0)

这是我的解决方法。

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_requestself._async_session为None时那样,将调用await session._init()并且请求将重试。

相关问题