asyncio仅在IO密集且协程能主动让出控制权时优于同步;混用阻塞调用会卡死事件循环;asyncio.run()每次新建并关闭循环,重复调用或在非主线程误用get_event_loop()将报错;阻塞IO需替换为aiohttp、asyncio.sleep等异步方案;并发需用gather或create_task而非直接await。

asyncio 不是“多线程替代品”,它只在 IO 密集、协程能真正让出控制权的场景下才比同步快;直接套用 async/await 却混用阻塞调用(比如 time.sleep() 或未包装的 requests.get()),事件循环会被卡住,性能反而更差。
为什么 asyncio.run() 启动后就报 “Event loop closed” 或 “RuntimeError: no running event loop”
这是最常见的启动错误:你在已关闭的事件循环里又试图获取或运行它。典型场景是——在 Jupyter Notebook 里反复执行 asyncio.run(main()),或在子线程中手动调用 asyncio.get_event_loop() 而没做循环管理。
-
asyncio.run()每次都会新建一个事件循环、运行完自动关闭,不能复用;重复调用会因前一个循环已关闭而失败 - Jupyter 默认使用
asyncio兼容模式(如nest_asyncio),但未安装时直接运行会触发RuntimeError - 在非主线程中,
asyncio.get_event_loop()默认返回None,必须显式用asyncio.new_event_loop()并set_event_loop()
解决办法:开发阶段优先用 asyncio.run();调试多线程或嵌入环境时,改用 asyncio.get_running_loop()(Python 3.7+)或显式管理循环生命周期。
如何把阻塞 IO(如 requests、time.sleep)变成真正的异步调用
asyncio 本身不改造第三方库——requests、json.load()、time.sleep() 全是同步阻塞函数,直接 await 它们会报 TypeError: object XXX can't be used in 'await' expression,或者更隐蔽地让整个事件循环卡死。
立即学习“Python免费学习笔记(深入)”;
- 网络请求:换用
aiohttp或httpx.AsyncClient,它们原生支持await client.get(...) - 延时:用
await asyncio.sleep(1),不是time.sleep(1) - 文件读写:避免
open().read(),改用asyncio.to_thread()(Python 3.9+)包装,或用aiofiles库 - CPU 密集任务:必须用
loop.run_in_executor()拆到线程池/进程池,否则协程无法让出控制权
示例错例:await requests.get("https://httpbin.org/delay/1") —— 这行根本跑不通;正确写法是 async with session.get("https://httpbin.org/delay/1") as r:(配合 aiohttp.ClientSession)。
asyncio.create_task() 和 await coro 的行为差异直接影响并发效果
是否并发,不取决于有没有 async,而取决于你“怎么调度”协程。写成 await f1(); await f2() 是串行;写成 await asyncio.gather(f1(), f2()) 或先 create_task() 再 await,才是并发。
-
await coro():立刻执行该协程,当前协程挂起,直到它完成——等效于同步调用 -
asyncio.create_task(coro()):把协程提交进事件循环队列,立即返回Task对象,不等待执行结果 -
asyncio.gather():批量启动多个协程并等待全部完成,内部自动创建 task;适合“发完一起等”场景 - 注意
create_task()返回的 task 必须被await或加入gather,否则可能被垃圾回收导致任务静默取消
简单验证:用 time.time() 包裹两段耗时 IO,串行耗时约 2 秒,用 gather 并发后稳定在 1 秒左右——这才是 asyncio 提效的关键路径。
真正难的不是写 async 函数,而是识别哪些调用可让出、哪些必须隔离、哪些根本不能进事件循环。很多“异步变慢”的问题,根源是把同步逻辑硬塞进 async 壳子里,却没动底层 IO 模式。



















