RuntimeError при запуске сопрограммы из __init__ - PullRequest
2 голосов
/ 28 марта 2019

Вот пример кода.

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 ]

2 голосов
/ 28 марта 2019

Если какой-то метод использует что-то асинхронное, оно также должно быть явно определено как асинхронное.Это основная идея asyncio: заставить вас писать код так, чтобы вы всегда знали, может ли какой-нибудь произвольный метод сделать что-то асинхронное.

В вашем фрагменте вы хотите сделать асинхронную вещь (bar I / O) метод внутренней синхронизации __init__ и asyncio запрещает это.Вы должны выполнить _run_coro async и инициализировать Foo асинхронно, например, , используя __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+

Возможно, вам будет интересно прочитать этот ответ чтобы лучше понять, как работает asyncio и как с этим справиться.

Upd:

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

Не делайте таких вещей: метод генератора - это только детали реализации в отношении сопрограмм.Вы можете предсказать, как сопрограмма отреагирует на вызов метода .send(), и вы можете положиться на это поведение.

Если вы хотите выполнить сопрограмму, используйте await, если вы хотите запустить ее "в фоновом режиме", используйте задача или другие функции из asyncio документ .

Какой будет правильный способ инициализации 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())

Upd2:

Вы можете комбинировать способы инициализации / закрытия объекта сверху для достижения желаемого результата.Пока вы помните, что обе операции асинхронны и, следовательно, их следует ожидать, все должно быть в порядке.

Еще один возможный способ:

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())
0 голосов
/ 09 апреля 2019

Вот мое решение.

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() и запрос будет повторен.

...