Как обеспечить существование только одной сопрограммы определенного типа

#python #python-3.x #python-asyncio

#python #python-3.x #python-asyncio

Вопрос:

в моем классе у меня есть метод, который извлекает веб-сайт (виден ниже).

Я заметил, что другие методы, использующие этот метод, могут привести к открытию нескольких запросов к одному сайту (когда один запрос ожидает self._page по-прежнему отсутствует).

Как я могу этого избежать?

Я имею в виду, когда есть еще один вызов _get_page, но он находится в ожидании, просто верните future из первого и не повторяйте запрос страницы

 async def _get_page(self) -> HtmlElement:
        if self._page is None:
            async with self._get_session().get(self._url) as page:
                self._page = lxml.html.document_fromstring(await page.text())
        return self._page
  

Ответ №1:

Как я могу избежать [множественных запросов]?

Вы могли бы использовать asyncio.Lock :

 saync def __init__(self, ...):
    ...
    self._page_lock = asyncio.Lock()

async def _get_page(self) -> HtmlElement:
    async with self._page_lock:
        if self._page is None:
            async with self._get_session().get(self._url) as page:
                self._page = lxml.html.document_fromstring(await page.text())
    return self._page
  

Комментарии:

1. Это, безусловно, более простое решение, чем мое. Кстати, это генерирует: AttributeError: module 'lxml' has no attribute 'html' (исходный код OP).

2. Я принял запрос OP за чистую монету и вернул Будущее, которое не блокируется.

3. @Booboo Я не уверен, что вы подразумеваете под «возвращением будущего» — ваш _get_page , безусловно, ожидает будущего и возвращает фактический результат, как и мой. (Обратите внимание, что вы могли бы просто return await future потому, что ожидание будущего немедленно оценивается по его результату.) Ваш код дублирует функциональность, уже используемую в устройствах синхронизации asyncio, таких как Lock or Event , которые отслеживают список официантов очень похожим образом.

Ответ №2:

Обновление для Python 3.8 и jupyter notebook

 import asyncio
import aiohttp
from lxml import html


class MyClass:
    def __init__(self):
        self._url = 'https://www.google.com'
        self._page = None
        self._futures = []
        self._working = False
        self._session = aiohttp.ClientSession()


    async def _close(self):
        if self._session:
            session = self._session
            self._session = None
            await session.close()

    def _get_session(self):
        return self._session

    async def _get_page(self):
        if self._page is None:
            if self._working:
                print('will await current page request')
                loop = asyncio.get_event_loop()
                future = loop.create_future()
                self._futures.append(future)
                return await future
            else:
                self._working = True
            session = self._get_session()
            print('making url request')
            async with session.get(self._url) as page:
                print('status =', page.status)
                print('making page request')
                self._page = html.document_fromstring(await page.text())
                print('Got page text')
                for future in self._futures:
                    print('setting result to awaiting request')
                    future.set_result(self._page)
                self._futures = []
                self._working = False
        return self._page


async def main():
    futures = []
    m = MyClass()
    futures.append(asyncio.ensure_future(m._get_page()))
    futures.append(asyncio.ensure_future(m._get_page()))
    futures.append(asyncio.ensure_future(m._get_page()))
    results = await asyncio.gather(*futures)
    for result in results:
        print(result[0:80])
    await m._close()


if __name__ == '__main__':
    asyncio.run(main())
    #await main() # In jupyter notebook and iPython
  

Обратите внимание, что в Windows 10 я видел при завершении:

 RuntimeError: Event loop is closed
  

См. https://github.com/aio-libs/aiohttp/issues/4324

Комментарии:

1. Я обновил ответ, чтобы рассмотреть Python 3.8, Jupyter Notebook и некоторые ложные ошибки, которые вы могли видеть в Windows 10. Небольшая обратная связь была бы оценена.