Отмена задачи во внутренних компонентах asyncio

Я знаю, что task.cancel() ничего не делает, кроме установки флага, говорящего, что задача должна быть отменена (здесь), так что на следующей итерации цикла событий CancelledError попадает в обернутую сопрограмму.

Также я знаю, что выбрасывание исключений в сопрограммы происходит только при вызовах операторов await. Сопрограмма должна быть в этом состоянии.

Теперь взгляните на это:

import asyncio

async def coro_1():
    print("inside coro_1")
    try:
        await asyncio.sleep(0)
    except asyncio.CancelledError:
        print("cancellation error occurred")
        raise

async def main():
    print("inside main")
    new_task = asyncio.create_task(coro_1())
    print("finishing main")

asyncio.run(main())

выход:

inside main
finishing main
inside coro_1
cancellation error occurred

В main() сопрограмме я создал новую задачу, но не дал ей возможности запуститься. (ничего не ждал). Сопрограмма main() завершается, но выполняется coro_1!

Хорошо, я знаю, что это произошло, потому что я использовал asyncio.run(), но после того, как я просмотрел исходный код, я не мог понять, почему и где. Я должен быть определенно неправ в одном из следующих шагов.

  1. когда задача coro_1() создается в main, она планирует новый обратный вызов (self.__step) в цикле событий для следующего цикла. (в очереди self._ready).

  2. main завершен, выполнение переходит в метод __exit__ объекта Runner (здесь).

  3. тогда .cancel()все оставшиеся задачи здесь. Теперь у нас есть наша coro_1 задача. Его флаг _must_cancel установлен, и его __step ожидает выполнения на следующей итерации с этим набором исключений.

  4. Поскольку exc установлен, мы достигаем в этой строки:

    ...
    else:
        result = coro.throw(exc)
    ...
    

Теперь, как это возможно? наш coro_1 не в том состоянии, когда мы можем выдать ему исключение. Кто-то должен был выполнить его до строки await asyncio.sleep(0) в прошлом. кто и где?

Я использую Python 3.11.

Заранее спасибо.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я нашел ответ.

Во-первых, в цикле событий есть очередь с именем self._ready(), в которой все обратные вызовы готовы к выполнению.

Вот шаги:

  1. Когда мы запускаем нашу main сопрограмму с помощью .run(), она создает новую задачу, добавляет свой обратный вызов в self._ready, а затем вызывает run_until_complete(). Наш self._ready содержит только этот обратный вызов.

  2. В run_until_complete он добавляет _run_until_complete_cb обратный вызов к add_done_callback будущего (это будет называться «будущее готово»). какова его работа? он останавливает цикл событий. в основном он устанавливает атрибут self._stopping цикла событий в True, который используется здесь.

  3. Теперь он вызывает run_forever(), который действительно перебирает очередь self._ready и запускает все обратные вызовы. (сейчас у нас есть только одна main сопрограмма).

  4. Обратите внимание, что обратные вызовы извлекаются из self._ready для вызова, поэтому self._ready пуст, когда выполнение достигает строки new_task = asyncio.create_task(coro_1()). Затем он добавляет новый обратный вызов к self._ready, который отвечает за запуск coro_1 сопрограммы.

  5. main() достигает конца, и все готово. что будет дальше? пришло время запланировать его _run_until_complete_cb, который мы зарегистрировали ранее.

  6. А что внутри нашего self._ready ? два обратных вызова, один для выполнения coro_1 и один для выполнения _run_until_complete_cb. Цикл еще не остановлен. Таким образом, запускается еще один цикл цикла. Он выполняет coro_1 до оператора await (теперь он готов к исключению CancelledError), а также выполняет _run_until_complete_cb. Этот последний обратный вызов останавливает цикл событий. что внутри нашего self._ready? только один обратный вызов, который отвечает за продолжение coro_1. (он запланировал себя обратно в цикл событий).

  7. Теперь пришло время вызвать метод __exit__ объекта Runner. Что оно делает? это отменяет все оставшиеся задачи. наша единственная задача coro_1, которая находится на рассмотрении. Теперь в третьем цикле цикла событий он бросает CancelledError в coro_1.

Другие вопросы по теме