You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何asyncio中call_soon失效但create_task可成功执行协程?

为什么call_soon无法执行协程,而create_task可以?

好问题!咱们一步步拆解这个现象背后的逻辑,帮你理清事件循环、call_soon和create_task的核心区别。

首先纠正一个关键误解:call_soon的回调不处理协程

你代码里的loop.call_soon(print_world)其实犯了一个容易忽略的错误:call_soon的设计是用来添加普通可调用对象(比如普通函数),而非协程。当事件循环执行这个回调时,它只会调用print_world()生成一个协程对象,但不会自动去await这个协程——这才是你看到“coroutine was never awaited”警告的直接原因,不是事件循环没迭代。

换句话说,call_soon根本不知道怎么处理协程,它只是执行你传入的可调用对象,剩下的事就不管了。

为什么asyncio.create_task能正常工作?

asyncio.create_task()(或者loop.create_task())的核心作用是:把协程对象包装成一个Task实例,然后立刻将这个Task加入事件循环的任务队列,等待调度执行。

Task是事件循环会主动跟踪的对象——只要事件循环还在运行,它就会不断从任务队列里取出就绪的Task并执行,直到Task完成。

run_until_complete的实际行为:不是“任务一完成就退出”

你提到的run_until_complete的文档描述确实容易让人误解,它的实际逻辑是:

  1. 把你传入的协程包装成一个主Task,开始执行;
  2. 当主Task完成后,事件循环并不会立刻停止,而是会继续处理所有已存在的就绪Task和回调;
  3. 直到任务队列和回调队列都为空,才会彻底退出事件循环。

结合你的代码场景:

  • 当你用create_task(print_world())时,这个Task会被加入任务队列。当print_hello的主Task完成后,事件循环会继续执行这个新Task,所以你能看到“world!”输出;
  • 当你用call_soon(print_world)时,事件循环执行回调生成了协程,但这个协程没有被包装成Task,事件循环不会主动处理它,所以协程永远没被await,触发警告。

针对你的实际场景(Django信号中调用异步方法)

因为Django信号是同步方法,在事件循环已运行的情况下,正确的做法是用asyncio.create_task()(或者loop.create_task())把异步方法包装成Task,这样事件循环会自动调度执行这个Task。绝对不要用call_soon直接传入协程函数,因为它无法处理协程的异步调度。

举个适配你场景的示例:

import asyncio

def django_signal_handler():  # 同步的Django信号方法
    loop = asyncio.get_running_loop()
    loop.create_task(print_world())  # 用create_task包装协程

async def print_world():
    print("world!")

async def main():
    print("hello!")
    django_signal_handler()

asyncio.run(main())

内容的提问来源于stack exchange,提问作者Silversonic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 06:57:41