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

Python asyncio call_soon:阻塞与非阻塞调用混合执行的疑问

Asyncio call_soon 执行行为解析

核心问题解答

1. 为何第一段代码总时长为3秒而非4秒?

这是因为asyncio的事件循环是单线程运行的,且call_soon注册的同步回调会在协程让出控制权后立即执行,同步阻塞代码会卡住整个事件循环,直接覆盖了异步sleep的等待时间。具体执行流程如下:

  1. 事件循环启动amain协程,调用loop.call_soon(blocking)将blocking函数加入即时回调队列。
  2. amain执行await nonblocking(),进入nonblocking协程:
    • 打印nonblocking called,随后执行await asyncio.sleep(1)——这会向事件循环注册一个1秒后的定时唤醒任务,同时nonblocking协程暂停,让出控制权给事件循环。
  3. 事件循环检查定时任务尚未到期(刚注册不足1秒),转而处理即时回调队列中的blocking函数:
    • blocking开始执行,time.sleep(3)是同步阻塞操作,会完全卡住事件循环,期间无法处理任何异步任务(包括那个1秒后的定时唤醒)。
  4. 3秒后blocking执行完毕,事件循环重新检查定时任务,发现asyncio.sleep(1)的唤醒时间早已过期,立即恢复nonblocking协程,打印nonblocking done后协程结束。
  5. amain协程结束,事件循环关闭。

整个过程中,blocking的3秒阻塞完全覆盖了nonblocking中1秒的异步等待,因此总耗时为3秒。

2. call_soon在此场景下的实际作用是什么?

loop.call_soon(func)的核心作用是向asyncio事件循环注册一个同步回调函数,该函数会在事件循环的下一个“可执行回调”时机(即当前协程让出控制权后)被同步执行。

需要重点注意两点:

  • 回调是同步执行的:如果回调包含time.sleep()这类阻塞操作,会直接卡住整个事件循环,所有异步任务都会暂停,直到回调执行完毕。
  • 回调优先级高于普通协程任务:事件循环在处理完到期的定时任务和IO事件后,会优先执行call_soon注册的回调,再去处理任务队列中的协程。

额外场景解析

场景1:替换为await asyncio.gather(nonblocking(), nonblocking(), nonblocking())后总时长4秒

当使用gather同时调度3个nonblocking协程时,执行流程变为:

  1. call_soon(blocking)将回调加入队列后,gather启动3个nonblocking协程,每个协程都执行到await asyncio.sleep(1)并暂停,注册3个1秒后的定时任务。
  2. 事件循环等待1秒,直到3个定时任务到期,依次唤醒3个nonblocking协程,完成后gather结束。
  3. 事件循环此时才处理call_soon注册的blocking回调,执行3秒的同步阻塞。

总耗时为1秒(异步等待)+3秒(同步阻塞)=4秒,核心原因是事件循环会先处理完所有到期的定时任务,再执行call_soon的回调。

场景2:添加第二个loop.call_soon(blocking)后,先执行第一个blocking和nonblocking,3秒后执行第二个blocking

call_soon注册的回调是按顺序排队执行的:

  1. 两个blocking函数被依次加入即时回调队列。
  2. 当nonblocking协程让出控制权后,事件循环先执行队列中的第一个blocking,耗时3秒。
  3. 第一个blocking执行完毕后,事件循环处理nonblocking的定时任务(已过期),完成nonblocking协程。
  4. 最后事件循环才执行队列中的第二个blocking函数。

这是因为事件循环会在处理完当前回调后,再依次处理队列中的下一个回调,且同步回调的执行会阻塞整个循环,必须等前一个回调完成才能执行下一个。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 01:30:58