#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
требуется асинхронная функция для запуска на следующей итерации цикла событий. Спасибо.