为何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的文档描述确实容易让人误解,它的实际逻辑是:
- 把你传入的协程包装成一个主Task,开始执行;
- 当主Task完成后,事件循环并不会立刻停止,而是会继续处理所有已存在的就绪Task和回调;
- 直到任务队列和回调队列都为空,才会彻底退出事件循环。
结合你的代码场景:
- 当你用
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

