#python #python-3.x #python-asyncio
Вопрос:
Каков стандартный способ в Python гарантировать, что все параллельные задачи будут выполнены до завершения цикла событий? Вот упрощенный пример:
import asyncio
async def foo(delay):
print("Start foo.") # Eg: Send message
asyncio.create_task(bar(delay))
print("End foo.")
async def bar(delay):
print("Start bar.")
await asyncio.sleep(delay)
print("End bar.") # Eg: Delete message after delay
def main():
asyncio.run(foo(2))
if __name__ == "__main__":
main()
Выходной ток:
Start foo. # Eg: Send message
End foo.
Start bar.
Желаемый результат:
Start foo. # Eg: Send message
End foo.
Start bar.
End bar. # Eg: Delete message after delay
Я попытался запустить все оставшиеся задачи после loop.run_until_complete()
этого , но это не сработает, так как к тому времени цикл будет завершен. Я также попытался изменить основную функцию следующим образом:
async def main():
await foo(2)
tasks = asyncio.all_tasks()
if len(tasks) > 0:
await asyncio.wait(tasks)
if __name__ == "__main__":
asyncio.run(main())
Вывод правильный, но он никогда не завершается, так как сопрограмма main()
является одной из задач. Описанная выше настройка также описывает, как discord.py
отправляет сообщение и удаляет его через определенный промежуток времени, за исключением того, что оно используется loop.run_forever()
вместо этого, поэтому проблема не возникает.
Комментарии:
1. Попробуйте:
task = [asyncio.create_task(bar(delay))]
а затемawait asyncio.gather(*task, return_exceptions=False)
в вашей функции foo.2. Обратите внимание, что в эти дни вы не должны напрямую взаимодействовать с циклом. Используйте
asyncio.run
вместо получения и запуска цикла вручную.3. @user56700 Я попробовал, но он блокируется
foo
до тех пор, пока панель не завершится.bar
должно выполняться одновременно. Примером может служить бот, отправляющий сообщение с истекшим сроком действия. Функция отправки сообщения должна выполняться как можно быстрее и возвращаться (вызывающая функция хочет только дождаться отправки сообщения, а не истечения срока действия сообщения).4. @MisterMiyagi Спасибо, я отредактировал сообщение с улучшенным кодом.
Ответ №1:
Не существует стандартного способа дождаться всех задач в asyncio
(и аналогичных фреймворках), и на самом деле не следует пытаться. Говоря в терминах потоков, a Task
выражает как регулярные, так и демонические действия. Ожидание всех задач без разбора может привести к остановке приложения на неопределенный срок.
Задача, которая создается, но никогда await
не редактируется, де-факто является фоновой/демонической задачей. Напротив, если задача не должна рассматриваться как фоновая/демоническая, то ответственность за ее выполнение лежит на вызывающих абонентах await
.
Самое простое решение-для каждой сопрограммы await
отменить и/или отменить все задачи, которые она порождает.
async def foo(delay):
print("Start foo.")
task = asyncio.create_task(bar(delay))
print("End foo.")
await task # foo is done here, it ensures the other task finishes as well
Поскольку весь смысл async
/задач заключается в дешевом переключении задач, это дешевая операция. Это также не должно влиять на какие-либо хорошо продуманные приложения:
- Если целью функции является создание значения, любые дочерние задачи должны быть частью создания этого значения.
- Если целью функции является какой-либо побочный эффект, любые дочерние задачи должны быть частями этого побочного эффекта.
Для более сложных ситуаций может быть целесообразно вернуть любые невыполненные задачи.
async def foo(delay):
print("Start foo.")
task = asyncio.create_task(bar(delay))
print("End foo.")
return task # allow the caller to wait for our child tasks
Это требует от вызывающего абонента явной обработки нерешенных задач, но дает быстрые ответы и максимальный контроль. Задача верхнего уровня затем отвечает за обработку любых сиротских задач.
Для async
программирования в целом парадигма структурированного программирования кодирует идею «обработки нерешенных задач» в объекте управления. В Python этот шаблон был закодирован trio
библиотекой в виде так называемых Nursery
объектов.
import trio
async def foo(delay, nursery):
print("Start foo.")
# spawning a task via a nursery means *someone* awaits it
nursery.start_soon(bar, delay)
print("End foo.")
async def bar(delay):
print("Start bar.")
await trio.sleep(delay)
print("End bar.")
async def main():
# a task may spawn a nursery and pass it to child tasks
async with trio.open_nursery() as nursery:
await foo(2, nursery)
if __name__ == "__main__":
trio.run(main)
Хотя эта модель была предложена для asyncio
as TaskGroups
, до сих пор она была отложена.
Однако различные порты шаблона для asyncio
доступны через сторонние библиотеки.