asyncio.wait 报 Passing coroutines is forbidden,先检查传入对象
Python 3.11 起 asyncio.wait 不接受裸协程。正确创建并持有 Task,处理 done、pending 与取消,核对第三方库已发布修复。
升级 Python 后,原来的 asyncio.wait([task, event.wait()]) 报 TypeError: Passing coroutines is forbidden, use tasks explicitly.。列表里看起来都是能等待的对象,但 event.wait() 返回裸协程,不能直接交给 Python 3.11 及以上的 asyncio.wait。
需要修的是输入对象和任务生命周期。补一个 await,或随手把协程全部启动后不再管理,都可能改变原来“等待任意一个先完成”的逻辑。
找到列表里尚未调度的协程
async def 函数调用后的对象、asyncio.Event.wait() 等调用结果可能是协程;已经创建的 Task 和 Future 则是另外两种对象。wait 要求非空的 Task/Future 集合,不能把“可以 await”直接当作其输入要求。
自己创建的协程可以在正在运行的事件循环里用 asyncio.create_task() 调度,并保存返回的 Task。已经是 Future 的对象不用再次传给 create_task;后者接收的是协程,不是任意可等待对象。
如果 traceback 指向第三方库,先核对库版本。huggingface_hub 0.32.3 的 tiny-agents 输入等待路径曾传入裸 exit_event.wait();0.32.4 已加入对应的 Task 创建修复。受影响的旧环境应按项目依赖约束更新到包含修复的兼容版本,不需要修改 site-packages。这项版本边界不能用于其他库的同名错误,也不意味着最新版本还存在同一缺陷。
wait 返回 pending,不负责替你关闭任务
wait(..., timeout=...) 超时后返回 done 和 pending,不会自动抛出 TimeoutError,也不会自动取消 pending。FIRST_COMPLETED 返回时,其他任务同样可能仍在执行;外层等待被取消,也不代表输入 Task 全部结束。
下面演示自己拥有两个新任务、决定返回前停止剩余任务的结构。它使用 Python 3.11 及以上,load_value 只是可取消的异步示例,不涉及线程或真实外部操作:
import asyncio
async def load_value():
await asyncio.sleep(1)
return 42
async def wait_for_owned_work(exit_event):
work_task = asyncio.create_task(load_value())
exit_task = asyncio.create_task(exit_event.wait())
tasks = {work_task, exit_task}
try:
done, pending = await asyncio.wait(
tasks,
timeout=5,
return_when=asyncio.FIRST_COMPLETED,
)
if not done:
raise TimeoutError("work did not finish within 5 seconds")
if work_task in done:
return work_task.result()
exit_task.result()
return None
finally:
for task in tasks:
if not task.done():
task.cancel()
await asyncio.gather(*tasks, return_exceptions=True)
超时异常由这段业务代码主动抛出;wait 本身只返回集合。已完成任务用 result() 取回业务结果或抛出其异常。finally 取消并等待自己拥有的任务,避免只调用 cancel() 就退出,把资源清理留在后台。
这里没有吞掉调用方的 CancelledError。任务若自行压住取消,清理也可能继续等待;实际 I/O 应有合理超时和退出机制。同步线程里的工作另有生命周期,不能用这段示例保证它被强制停止。
借来的 Task 要保留原来的所有权
任务来自连接管理者、缓存或其他调用方时,不能照搬上面“全部取消”的流程。共享任务可能还有其他等待者,应先决定谁有权取消,谁继续收取结果和异常。若需要让未完成任务继续,必须保留强引用并交给明确的管理者,不能只丢弃 pending。
还要检查原来使用的是 FIRST_COMPLETED、FIRST_EXCEPTION 还是默认全部完成。改成 gather 会改变返回和失败行为;在列表生成式里逐个 await 又可能变成串行执行。先保留业务需要的等待条件,再修正对象类型。
在隔离测试中分别检查正常完成、退出事件先到、超时和调用方取消。每个结果都应有对应处理,自己拥有的残留任务和资源应完成清理。修改前保留原等待及取消逻辑,回退时也要核对已经启动的任务,不能用重新创建协程掩盖还在执行的旧工作。


