返回专题首页

Python 专题

asyncio 入门:事件循环、协程调度与异步编程模型

如果说上一节解决的是“有几种并发方式”,那么这一节要解决的就是“Python 的异步模型到底是怎么运转的”。很多人会写 `async` 和 `await`,但对事件循环、任务调度、取消和异常传播并没有形成稳定理解。

Python 专题第 29 篇 / 39 篇4 分钟

如果说上一节解决的是“有几种并发方式”,那么这一节要解决的就是“Python 的异步模型到底是怎么运转的”。很多人会写 asyncawait,但对事件循环、任务调度、取消和异常传播并没有形成稳定理解。

这一节我们会把 asyncio 拆开来看,让你真正理解异步代码不是“换个语法写同步逻辑”,而是一种新的执行模型。理解这一点之后,后面的异步请求、异步框架和高并发服务才会变得顺理成章。

为什么要学习 asyncio

只要你面对的是高并发 I/O 场景,asyncio 就很值得学习。因为它解决的是:在单线程里如何高效地协调大量等待型任务。

如果没有异步模型,你可能会:

  • 每个请求开一个线程;
  • 一路阻塞等待结果;
  • 在大量连接场景下付出较高的线程成本。

asyncio 让很多“等待中”的任务可以在同一个事件循环里交替推进。

所以它的价值,不是让语法变花,而是改变了调度方式。

asyncawait 的基本含义

async def 定义的是协程函数,调用后不会立刻执行到底,而是得到一个协程对象。

await 则表示:当前协程在这里等待某个可等待对象完成,并把执行机会让回事件循环。

这说明异步代码的关键不是关键字本身,而是你开始显式标出“这里会等待”“这里可以切换到别的任务”。

一旦接受了这套心智,你就不会再把异步代码当成“同步代码改改语法”的版本。

事件循环与任务调度

事件循环可以先理解成整个异步世界的“调度中心”。它负责管理一批待运行任务,在合适时机推进它们。

create_task

当你希望某个协程被调度执行,而不是只是拿着它的对象时,就会用到任务创建能力。

create_task 的意义,是把协程包装成可被事件循环管理的任务。

这非常关键,因为“定义了协程”“拿到了协程对象”和“它真的开始被调度执行”是三件不同的事。

gather 与并发等待

当你要并发等待多个异步操作时,gather 是很常见的工具。它允许你把多个任务一起交给事件循环推进,并在最终统一收集结果。

但要注意,这种“并发等待”并不意味着所有任务真的并行跑在多个 CPU 上,它更像是多个 I/O 等待型任务在时间上交错推进。

取消、超时与异常传播

异步编程里最容易被忽视的一点,是任务不总是顺利完成。

它可能:

  • 被取消;
  • 超时;
  • 中途抛异常;
  • 某个子任务失败导致整组任务策略变化。

所以如果你只会写 happy path,异步代码在真实场景里就会很脆弱。

特别是取消和超时,它们不是“边角案例”,而是异步系统里天然会出现的控制流分支。你需要提前设计:

  • 超时后如何清理资源;
  • 子任务失败是否影响整体;
  • 取消信号是否应该继续向下传播。

异步代码的调试与常见坑

异步代码调试难,通常不是因为语法本身,而是因为执行顺序不再完全线性。

最常见的坑包括:

  • 忘了 await
  • 在异步链路里混入阻塞调用;
  • 任务创建了却没正确等待;
  • 异常在后台任务里悄悄丢失。

所以异步代码最重要的工程习惯之一,是保持调用边界清晰。你要很明确地知道:

  • 哪些函数是异步的;
  • 哪些地方会并发启动任务;
  • 哪些地方负责等待结果和收尾。

总结与预告

这一节我们把异步编程从“会写 async/await”推进到了“理解执行模型”。事件循环、任务调度、超时和取消一旦建立了清晰心智,后面的异步框架和高并发场景就不会再显得神秘。

下一节我们会把这种模型落到更具体的应用上,从同步和异步两个方向来看一层更可靠的 HTTP 请求封装。