
asyncio.run() 会在主协程结束后自动取消所有未完成的后台任务,并抛出 CancelledError,因此通常无需手动调用 .cancel();但为保障资源清理与逻辑完整性,建议显式捕获并处理该异常。
`asyncio.run()` 会在主协程结束后自动取消所有未完成的后台任务,并抛出 `cancellederror`,因此通常无需手动调用 `.cancel()`;但为保障资源清理与逻辑完整性,建议显式捕获并处理该异常。
在使用 asyncio.create_task() 启动后台任务时,一个常见误区是认为“只要主程序结束,任务就会静默终止”。实际上,asyncio.run() 在主协程(如 main())返回后,会进入事件循环关闭流程:它会主动取消所有仍在运行的、非守护型(non-daemon)Task 实例,并等待它们响应取消信号(即抛出或传播 asyncio.CancelledError)。这一行为是明确且可预期的,也是 Python 官方文档中定义的“有序关闭”(orderly shutdown)的一部分。
以下是一个验证该机制的增强版示例:
import asyncio
import random
import time
async def forever_while_loop_task():
try:
while True:
sleep_seconds = random.randint(5, 30)
print(f"→ sleeping for {sleep_seconds} seconds")
await asyncio.sleep(sleep_seconds)
print("→ looping")
except asyncio.CancelledError:
print("⚠️ The forever_while_loop_task was cancelled — performing cleanup...")
# ✅ 此处可释放连接、关闭文件、提交日志等
await asyncio.sleep(0.1) # 模拟轻量清理耗时
print("✅ Cleanup completed.")
raise # 必须重新抛出,否则取消信号被吞没
async def main():
start = time.monotonic()
task = asyncio.create_task(forever_while_loop_task())
await asyncio.sleep(20)
print(f"✔️ ok done with program, took {time.monotonic() - start:.2f}s")
# 注意:无需 task.cancel() — asyncio.run() 会自动处理
if __name__ == "__main__":
asyncio.run(main())
运行结果将清晰显示取消路径:
→ sleeping for 18 seconds ✔️ ok done with program, took 20.00s → looping → sleeping for 22 seconds ⚠️ The forever_while_loop_task was cancelled — performing cleanup... ✅ Cleanup completed.
? 关键注意事项:
- ❌ 不要静默吞掉 CancelledError:若在 except asyncio.CancelledError: 中不 raise,任务将“假死”——看似被取消,实则协程未真正退出,可能导致资源泄漏或事件循环无法彻底关闭。
- ✅ 善用 finally 块:对于必须执行的清理操作(如 asyncio.Lock.release()、aiohttp.ClientSession.close()),应放在 try/finally 中,确保无论是否被取消都会执行。
- ⚠️ 避免依赖隐式取消:在生产服务(如 FastAPI 后台任务、长期运行的监控代理)中,推荐使用更结构化的生命周期管理,例如 asyncio.TaskGroup(Python 3.11+)或显式 shutdown 协程配合信号监听(如 signal.SIGTERM)。
- ? 不同任务启动方式的差异:
- asyncio.create_task():任务独立于调用者生命周期,需依赖 asyncio.run() 的全局清理;
- asyncio.gather(*tasks, return_exceptions=False):任一任务失败或取消将导致其余任务被立即取消;
- async with asyncio.TaskGroup() as tg:(推荐):退出 with 块时自动 cancel() 所有子任务,并聚合异常,语义更清晰、可控性更强。
总之,asyncio.run() 提供了开箱即用的安全关闭保障,但真正的“有序”不仅在于取消,更在于响应取消并完成收尾。将 CancelledError 视为正常控制流的一部分,而非错误,是编写健壮异步应用的关键实践。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











