сбой call_soon в asyncio, когда не выполняется create_task

#python-asyncio #python-3.7

#python-asyncio #python-3.7

Вопрос:

 import asyncio


def add_world_task():
    loop = asyncio.get_running_loop()
    loop.call_soon(print_world)
    # asyncio.create_task(print_world())  # <-- This is a "fix",


async def print_hello():
    print("hello!")
    add_world_task()


async def print_world():
    print("world!")

loop = asyncio.new_event_loop()
loop.run_until_complete(print_hello())
  

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

 hello!
/usr/lib/python3.7/asyncio/events.py:88: RuntimeWarning: coroutine 'print_world' was never awaited
  self._context.run(self._callback, *self._args)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

Process finished with exit code 0
  

Для меня это имеет смысл, согласно документам для call_soon , обратные вызовы, помещенные в call_soon , будут выполняться на следующей итерации цикла событий. run_until_complete выполняется до тех пор, пока метод не будет завершен. Следовательно, после print_hello завершения цикла событий другой итерации не будет, и сопрограмма print_world не запускается.

Чего я не понимаю, так это почему asyncio.create_task(print_world()) удается выполнить данное run_until_complete определение. После print_hello завершения кажется, что сопрограмму print_world все еще удается запустить в цикле событий, в соответствии с run_until_complete документацией?

Вызвано ли это call_soon размещением сопрограммы в начале цикла событий, create_task размещением ее в конце — и run_until_complete фактическим завершением текущей задачи и оставшейся части итерации цикла событий?

(Вам может показаться странным, что я использую синхронный, add_world_task а не await print_world прямой. К сожалению, это очень похоже на мой реальный сценарий. У меня есть синхронный метод (метод сигнала Django), который должен запускать асинхронный метод, в то время как цикл событий, возможно, уже запущен. Это можно сделать, добавив сопрограмму в цикл выполнения событий)

Ответ №1:

Оба, call_soon и create_task , планируют обратный вызов в конце текущей итерации. Оба переходят к выполнению обратного вызова, потому что цикл событий полностью завершает каждую итерацию, прежде чем проверять, следует ли завершать из-за loop.stop() того, что он был вызван (как run_until_complete() это реализовано).

call_soon в вашем случае не работает по совершенно другой причине: он предназначен для выполнения неасинхронного обратного вызова, то есть обычной функции, которую он просто вызывает. Вы передаете ему сопрограммную (асинхронную) функцию, которая также является допустимой вызываемой, но простой вызов этого вызываемого объекта ничего не делает, он просто создает объект сопрограммы, который вы должны либо ожидать, либо передавать create_task . Поскольку call_soon ожидается, что обратный вызов будет выполняться побочным эффектом, он сбрасывает этот объект сопрограммы на пол, что приводит к тому, что он никогда не выполняется и отображается предупреждение.

Правильный способ запланировать выполнение асинхронной функции в ближайшее время — это именно то, как вы это исправили, передав ее результат asyncio.create_task .

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

1. Понятно. Да, я неправильно понял, думая, что call_soon требуется асинхронная функция для запуска на следующей итерации цикла событий. Спасибо.