Всегда постоянное количество задач в aiohttp (asyncio)

Есть такой код (пример рабочий):

import asyncio
import sys
import time
import traceback

import aiohttp

if sys.platform == 'win32':
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())


class App:

    def __init__(self):

        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                          'AppleWebKit/537.36 (KHTML, like Gecko) '
                          'Chrome/42.0.2311.135 Safari/537.36 Edge/12.246',
        }

        self.queue = asyncio.Queue()

        # Aiohttp
        self.timeout = 3.34   # Таймаут ожидания ответа от сервера
        self.max_tasks = 3    # Кол-во одновременных задач
        self.tasks = []       # Список задач
        self.session = None   # aiohttp session init

    async def start(self):

        # Объект сессии aiohttp
        self.session = self.get_connect_session()

        urls = ['http://httpbin.org/delay/3' for _ in range(10)]

        [await self.queue.put(item) for item in urls]

        await self.run_task()

        await self.session.close()

    async def task(self, task_num=''):

        try:
            while not self.queue.empty():
                url = await self.queue.get()

                start = time.time()

                async with self.session.get(url) as r:

                    if r.status == 200:

                        print(await r.text())
                        print(f'Task[{task_num}] - Request time: '
                              f'{round(time.time() - start, 2)} sec.')

                    else:
                        print(f'Error: [{task_num}] - [{r.status}:{r.reason}]')

                    print('-' * 50, '\n')

        except (
                aiohttp.ServerTimeoutError,
                aiohttp.ClientResponseError,
                aiohttp.ClientConnectionError,
                aiohttp.ClientPayloadError,
                aiohttp.ClientError,
                Exception
        ):
            print(traceback.format_exc())

    async def run_task(self):

        self.tasks = [
            asyncio.create_task(self.task(task_num=str(i)))
            for i in range(self.max_tasks)
        ]

        await asyncio.gather(*self.tasks, return_exceptions=True)

    def get_connect_session(self):

        return aiohttp.ClientSession(
            headers=self.headers,
            timeout=aiohttp.ClientTimeout(total=self.timeout),
        )


if __name__ == '__main__':

    # Main app init
    app = App()

    try:
        loop = asyncio.get_event_loop()
        loop.run_until_complete(app.start())
        loop.run_until_complete(asyncio.sleep(0.250))
        loop.close()

    except Exception as err:
        print(err)

    finally:
        pass

Метод run_task() запускает определенное количество задач, исходя из значения self.max_tasks. Если ответ от сервера приходит с большей задержкой, чем выставлена в self.timeout, то те таски, которые "словили" timeout, прекращают свою работу и в итоге из всего массива работать остается только 1-2 или вообще ни одного, и программа завершается, но очередь еще не пуста.

Вопрос: как правильно добраться до словивших исключения тасков и вернуть их обратно в массив, чтобы в массиве всегда было указанное количество задач до тех пор, пока очередь не опустеет естественным образом?


Ответы (1 шт):

Автор решения: eri

Ваша логика решается через wait для перезапуска тасков

async def run_task(self):
    self.tasks = [
        asyncio.create_task(self.task(task_num=str(i)))
        for i in range(self.max_tasks)
    ]

    while not self.queue.empty():
        done, pending = await asyncio.wait(self.tasks, return_when=FIRST_COMPLETED)
        self.tasks = pending + [
            asyncio.create_task(self.task(task_num=str(i)))
            for i in range(self.max_tasks-len(pending))
        ]

или переносом try except внутрь цикла

async def task(self, task_num=''):
    while not self.queue.empty():
        try:
            url = await self.queue.get()

            start = time.time()

            async with self.session.get(url) as r:

                if r.status == 200:

                    print(await r.text())
                    print(f'Task[{task_num}] - Request time: '
                          f'{round(time.time() - start, 2)} sec.')

                else:
                    print(f'Error: [{task_num}] - [{r.status}:{r.reason}]')

                print('-' * 50, '\n')

        except (
                aiohttp.ServerTimeoutError,
                aiohttp.ClientResponseError,
                aiohttp.ClientConnectionError,
                aiohttp.ClientPayloadError,
                aiohttp.ClientError,
                Exception
        ):
            print(traceback.format_exc())
→ Ссылка