asyncio 的 call_soon 在 create_task 没有的地方失败

asyncio's call_soon fails where create_task doesn't

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, callbacks placed in call_soon will be run on the next iteration of the event loop. 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 很奇怪。不幸的是,这很像我的真实场景。我有一个同步方法(a Django 信号方法)需要 运行 异步方法,而事件循环可能已经 运行ning。它可以通过向 运行ning 事件循环添加协程来实现)

call_sooncreate_task 都在当前迭代结束时安排回调。由于事件循环 fully completes each iteration before examining whether to exit due to loop.stop() having been called (which is how run_until_complete() is implemented).

,两者都开始执行回调

call_soon 由于完全不同的原因在您的情况下不起作用:它旨在 运行 一个 non-async 回调,即它只是调用的普通函数。您向它传递一个协程(异步)函数,它也是一个有效的可调用函数,但仅调用该可调用函数不会 任何事情,它只是创建一个您应该等待的协程对象或传递给 create_task。由于 call_soon 期望回调通过副作用进行操作,因此它将协程对象丢在地上,导致它永远不会被执行并显示警告。

安排异步函数尽快执行的正确方法正是您通过传递其结果修复它的方式 asyncio.create_task