【问题标题】:asyncio's call_soon fails where create_task doesn'tasyncio 的 call_soon 失败,而 create_task 没有
【发布时间】:2020-09-09 13:01:00
【问题描述】:
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 信号方法)需要运行异步方法,而事件循环可能已经在运行。它可以通过向正在运行的事件循环添加协程来做到这一点)

【问题讨论】:

    标签: python-asyncio python-3.7


    【解决方案1】:

    call_sooncreate_task 都在当前迭代结束时安排回调。两者都开始执行回调,因为examining whether to exit 之前的事件循环fully completes each iteration 由于loop.stop() 已被调用(这就是run_until_complete()implemented)。

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

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

    【讨论】:

    • 明白。是的,我误认为call_soon 采用异步函数在下一次事件循环迭代中运行。谢谢。
    猜你喜欢
    • 2019-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-03
    • 2019-11-09
    • 2016-01-23
    相关资源
    最近更新 更多