asyncio的call_soon失败,而create_task没有

问题描述

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信号方法),它需要运行一个异步方法,而一个事件循环可能已经在运行。可以通过在正在运行的事件循环中添加协程来做到这一点

解决方法

call_sooncreate_task都将回调安排在当前迭代的末尾。两者都回避执行回调,因为在fully completes each iteration之前的事件循环examining whether to exit是由于调用了loop.stop()(这就是run_until_complete()implemented的原因)。 / p>

call_soon在您的情况下无法工作,原因完全不同:它旨在运行非异步回调,即它仅调用的普通函数。您向它传递了一个协程(异步)函数,该函数也是一个有效的可调用函数,但是仅调用该可调用函数不会任何事情,它只是创建一个协程对象,您应该等待该对象或将其传递给create_task。由于call_soon希望回调具有副作用,因此它将该协程对象放在地板上,这导致该对象永远不会执行并显示警告。

安排异步功能尽快执行的正确方法就是通过传递其结果asyncio.create_task来修复它的方法。