Asyncio gather и TaskGroup: соберите исключения конкурентных задач

Напишите решение на Python и проверьте его на странице.

Другие упражнения по этой теме: Asyncio TaskGroup и gather: задачи на ошибки и отмену корутин.

Аудит скрытых исключений в конкурентных задачах

Поисковый агрегатор опрашивает независимые архивы документов через конкурентные корутины. При переходе с asyncio.gather на asyncio.TaskGroup инженерам потребовалось наглядно сравнить поведение системы в моменты, когда сбои происходят сразу в нескольких источниках.

Для этого диспетчер запускает один и тот же набор имитационных корутин в двух режимах. Сначала опрос выполняется через gather с перехватом первого возникшего исключения, а затем — через TaskGroup. В ходе проверки подсчитывается, сколько реальных сбоев дочерних задач было поглощено и утеряно при работе с gather и сколько исключений сохранилось внутри составной группы ExceptionGroup при использовании TaskGroup.

Реализуйте функцию аудита, которая запускает оба сценария и возвращает количество скрытых исключений брошенных задач в gather вместе с полным списком исключений из ExceptionGroup в TaskGroup.

Что уже дано

В редакторе объявлена асинхронная функция audit_failures(factories), которую необходимо реализовать.

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

Что нужно сделать

Напишите асинхронную функцию audit_failures(factories).

Функция принимает список factories, содержащий от 0 до 50 вызываемых фабрик без аргументов. Каждый вызов фабрики возвращает новый независимый объект корутины для очередного запуска. Операции конечны, работают в текущем цикле событий и либо возвращают обычное значение, либо завершаются стандартным исключением Exception. Источники не выбрасывают вложенные ExceptionGroup.

Функция выполняет два последовательных запуска переданного набора фабрик:

  1. Первый запуск (asyncio.gather):
  • Для каждой фабрики создаётся новая корутина, и все они передаются в asyncio.gather(*..., return_exceptions=False).
  • При возникновении ошибки gather немедленно прерывается и пробрасывает первое случившееся исключение наружу.
  • Дождитесь полного завершения всех оставшихся задач этого запуска. Принудительно отменять их нельзя: они должны отработать свой жизненный цикл до конца.
  • Подсчитайте количество скрытых сбоев: число дочерних задач gather, которые завершились обычным исключением, но чьи ошибки не стали тем самым первым исключением, выброшенным вызовом await asyncio.gather(...). Если gather завершился без ошибок, это число равно 0. Считается именно количество упавших задач, а не число уникальных типов исключений.
  1. Второй запуск (asyncio.TaskGroup):
  • Для каждой фабрики заново вызывается конструктор корутины, и каждая корутина регистрируется как задача через tg.create_task(...) внутри блока async with asyncio.TaskGroup().
  • Если одна из задач падает, контекстный менеджер автоматически отменяет остальные задачи и по выходу выбрасывает составную группу ExceptionGroup.
  • Извлеките из возникшей ExceptionGroup плоский список исходных объектов исключений. В список входят только те ошибки, которые реально успели возникнуть в задачах группы (отменённые задачи не считаются упавшими с ошибкой источника). Если группа исключений не возникла, список должен быть пустым [].

Функция должна вернуть кортеж (hidden_count, group_errors):

  • hidden_count (int) — количество скрытых исключений брошенных задач первого запуска;
  • group_errors (list[Exception]) — плоский список исходных объектов исключений из ExceptionGroup второго запуска в произвольном порядке.

К моменту возврата из функции все запущенные ею задачи должны быть полностью завершены. Оставлять работающие задачи в цикле событий запрещено.

Ввод и вывод

Стандартные потоки stdin и stdout не используются: считывать ввод и вызывать print() не требуется. Тестирующая система импортирует объект верхнего уровня audit_failures после выполнения кода ученика и вызывает его в проверяющих асинхронных сценариях.

Пример использования

async def first():
    raise ValueError("first")

async def second():
    raise LookupError("second")

async def example():
    hidden, errors = await audit_failures([first, second])
    print(hidden)
    print(sorted(type(error).__name__ for error in errors))

asyncio.run(example())

Вывод:

1
['LookupError', 'ValueError']
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Связанные задачи