这是 asyncio 中任务被销毁但未完成的警告,主因是创建 Task 后未显式 await 或清理,导致协程“活儿没干完”就被终止。

Task was destroyed but it is pending 是什么问题
这个错误不是异常,而是一条警告(通常在程序退出时由 asyncio 主循环触发),意思是:某个 asyncio.Task 对象被销毁了,但它内部的协程还没执行完——也就是“任务被粗暴终止,活儿没干完”。
根本原因不是代码写错了,而是你创建了 Task 却没等它结束。常见于忘记 await、用 create_task() 后直接 return、或在 aexit / 程序退出前没做清理。
为什么 await task 不总是管用
await 只对协程对象有效;如果你调用的是 asyncio.create_task(),返回的是 Task 实例,await 它确实能等完成——但前提是这个 Task 还活着、没被取消、也没被丢弃。
容易踩的坑包括:
-
create_task()后没保存引用,变量被回收,Task 被 GC 掉但仍在运行 - 在
async def函数里启动 Task,然后函数提前return,Task 成为“孤儿” - 使用
asyncio.shield()或asyncio.wait_for()时超时取消了 Task,但没显式await task或检查task.done() - 在 Jupyter 或某些测试框架中,事件循环被反复启停,Task 残留未清理
确保 Task 执行完毕的实操方式
核心原则:所有手动创建的 Task,都要显式等待或取消。
- 如果你只需要等它完成,直接
await task(task是create_task()返回值) - 如果多个 Task 并发,用
await asyncio.gather(task1, task2, ...),它会自动等待全部完成 - 如果想“发出去就不管”,但又怕被销毁警告,改用
asyncio.create_task(..., name="xxx")并在程序退出前遍历asyncio.all_tasks()做清理 - 在类的生命周期中(比如
AsyncContextManager),务必在aexit里await或cancel()所有挂起的 Task - 测试时避免用
loop.run_until_complete(main())就完事,改用asyncio.run(main())——后者会自动清理未完成的 Task(Python 3.7+)
示例对比:
# ❌ 错误:task 被创建后没 await,函数结束,task 成为 pending
async def bad_example():
asyncio.create_task(some_coro())
return "done" # task 还在跑,但没人管
# ✅ 正确:显式 await,或用 gather 等待
async def good_example():
task = asyncio.create_task(some_coro())
await task # 确保完成
# 或:await asyncio.gather(task)
asyncio.run() 为什么有时不报错但仍有隐患
asyncio.run() 确实会在主协程结束后自动 cancel 所有 pending 的 Task,并 suppress 掉大部分警告——但这只是“掩盖”,不是“解决”。
- 如果 Task 内部有重要清理逻辑(比如关文件、发心跳、释放锁),被 cancel 后可能不会执行
- 某些异步库(如
aiohttp、aiomysql)的连接池依赖 Task 正常退出,强行 cancel 可能导致连接泄漏 - 日志里看不到警告 ≠ 没问题,只是被静默处理了
真正稳妥的做法,是让每个 Task 都有明确的生命周期归属:谁创建,谁 await;谁管理上下文,谁负责 cancel + await。
Task 的 pending 状态本身不可见,除非你主动检查 task.done() 或 task.cancelled()。别依赖自动机制兜底。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











