В каких ситуациях может возникать ошибка «asyncio.exceptions.CancelledError»?

Что может вызывать ошибку, когда я пытаюсь завершить бота кнопкой stop, вместо ctrl+c, хотя в коде нет бесконечных циклов? Возможно, что-то не так с функцией main?
  • 22 января 2024 г. 15:39
Ответы на вопрос 2
Ошибка `asyncio.exceptions.CancelledError` в Python возникает, когда асинхронная задача (task) отменяется. В asyncio это обычный способ прервать выполнение задачи.

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

1. Вы используете метод `cancel()` на объекте `asyncio.Task`. Это послание задаче, что ее исполнение следует прервать. Когда исполнение задачи возобновляется, она должна проверить свой статус, и если она была отменена, возбудить `CancelledError` в том месте, где она была приостановлена.

2. Вы останавливаете Event Loop, который управляет выполнением асинхронных задач, например, если используете `loop.stop()`. Все задачи, которые в этот момент не завершены, будут отменены, и в их контексте произойдет `CancelledError`.

3. Сервер или клиент, который вы запустили с помощью `asyncio`, закрылся. Многие асинхронные библиотеки при закрытии соединения отменяют все связанные с ним задачи.

Если вы пытаетесь остановить бота кнопкой stop (например, в IDE или кнопкой в пользовательском интерфейсе), и при этом возникает `CancelledError`, то, вероятно, ваша среда выполнения отменяет все выполняющиеся асинхронные задачи. Это стандартное поведение для многих сред, так как они стремятся корректно завершить все процессы.

Чтобы корректно обработать такую ошибку, вы можете обернуть ваш код в try/except блок:

```python
import asyncio

async def some_coroutine():
    try:
        # ваш асинхронный код
        ...
    except asyncio.CancelledError:
        # Обработка отмены задачи
        ...
        raise  # Перевыбросить исключение, если необходимо
```

Если бесконечных циклов в вашем коде нет, то причиной `CancelledError` может быть любое асинхронное действие, которое ожидает завершения. Наличие функции `main` как таковой не является причиной проблемы, скорее важно, как эта функция используется и останавливается.

Если вы предоставите пример кода, вызывающего такое поведение, я смогу дать более точные рекомендации по исправлению проблемы.
Открой доки и почитай. CancelledError выбрасывается в корутине, когда она завёрнута в таск через create_task() и на этом таске вызван метод cancel(). Это позволяет прервать выполнение корутины через выбрасывание специального исключения. Исключение не наследуется от Exception, поэтому обычный try-except его не ловит, если только специально не указать try ... except CancelledError. Так сделано, потому что при выходе через всплывание исключения будут отрабатывать все нормальные питоновские механизмы: блоки finally, блоки with и так далее. 
Я полагаю, команда на остановку бота делает cancel() на его главной корутине, скорее всего запущенной внутри start_polling(). Это исключение всплывает в твой main(). Но поскольку main() выполняется в asyncio.run(), то исключение всплывает туда. Это исключение не имеет особого смысла вне asyncio, так что я полагаю, run() ловит это исключение и вместо него выбрасывает KeyboardInterrupt() как ближайший не-асинхронный эквивалент. Это исключение всплывает на верхний уровень и останавливает интерпретатор.

Ты можешь ловить CancelledError в main(), чтобы спокойно завершить работу бота. Ну или ловить KeyboardInterrupt() в теле скрипта, на вызове asyncio.run().
Похожие вопросы