Python 异步编程实战:asyncio 事件循环与任务调度

举报
yd_226023071 发表于 2026/11/07 23:17:33 2026/11/07
【摘要】 在网络请求、文件读写和数据库访问越来越多的今天,程序常常不是算得慢,而是等得久。Python 的 asyncio 正是为这类等待而生的:它用单线程加事件循环的方式,让一个线程在等待时去做另一件事,从而把 I/O 等待时间变成有效工作时间。一、为什么需要异步同步代码遇到网络请求就会阻塞整个线程,一个请求耗时 200 毫秒,十个串行请求就是 2 秒。多线程能缓解问题,但线程切换、锁和共享状态会带...

在网络请求、文件读写和数据库访问越来越多的今天,程序常常不是算得慢,而是等得久。Python 的 asyncio 正是为这类等待而生的:它用单线程加事件循环的方式,让一个线程在等待时去做另一件事,从而把 I/O 等待时间变成有效工作时间。

一、为什么需要异步
同步代码遇到网络请求就会阻塞整个线程,一个请求耗时 200 毫秒,十个串行请求就是 2 秒。多线程能缓解问题,但线程切换、锁和共享状态会带来新的复杂度。异步则不同:当协程遇到 await,它主动把控制权交还给事件循环,循环立刻去执行其他已就绪的任务,等 I/O 完成后再回来继续,整个过程没有线程切换开销,也不必处处加锁。

二、事件循环是什么
事件循环是 asyncio 的核心调度器,它不断检查哪些任务已经就绪、哪些 I/O 已经完成,然后按顺序推进它们。可以把它理解成一个只需要一个工人的前台:工人一次只接待一个人,但客人填表等待时,工人可以先接待下一位,而不是原地空等。asyncio.run() 会自动创建并关闭事件循环,一般应用只需要一个入口,不建议自己反复创建循环。

三、协程与 await
用 async def 定义协程函数,调用它不会立即执行,只会得到一个协程对象,只有被 await 或者交给事件循环调度时才会真正运行。await 只能出现在 async def 内部,它表示这里可能等待。需要并发时,先用 asyncio.create_task() 把协程包装成任务,再用 asyncio.gather() 等待多个任务一起完成,这样十个请求的总耗时接近最慢的那一个,而不是十个之和。

四、任务调度的常用写法
常见的组合有三类。第一类是 gather:结果按传入顺序返回,适合批量拉取彼此独立的数据,配合 return_exceptions=True 可以让个别失败不影响整体。第二类是 wait:可以设定 FIRST_COMPLETED 或 ALL_COMPLETED,适合只关心最先完成的任务,或者分批处理。第三类是 as_completed:按完成顺序逐个产出结果,适合一边下载一边处理,越早完成的结果越早被消费。

五、超时与取消要显式处理
网络请求没有超时就是隐患。asyncio.wait_for() 可以给协程设定时限,超时会抛出 TimeoutError,调用方需要捕获并做好清理;直接用 task.cancel() 取消任务时,被取消的协程会在下一个 await 处收到 CancelledError,资源释放和 finally 块要写在协程内部。注意不要在 finally 里再做长时间阻塞等待,否则取消也会被拖住。生产代码还应当限制并发数,比如用 asyncio.Semaphore 把同时进行的请求控制在十个以内,避免瞬间打满上游接口。

六、容易踩的几个坑
第一个坑是在协程里调用 time.sleep() 或者同步的 requests 请求,这会阻塞整个事件循环,所有任务都被拖慢,应当改用 asyncio.sleep() 和支持异步的客户端。第二个坑是忘记 await,导致协程对象从未执行。第三个坑是把 CPU 密集型计算放进协程,事件循环仍然只有一个线程在跑,计算密集任务应当交给线程池或者进程池,用 loop.run_in_executor() 转移出去。

小结:异步不是万能药,它适合 I/O 密集的场景。掌握事件循环、协程、任务和超时这几块,就能写出可读又高效的异步代码。先从一个真实的批量请求场景练起,用 gather 加超时和信号量控制并发,效果往往立刻就能看见。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。