异步任务是一条有明确起点、中间状态和终点的事件路径,依赖状态推进与事件驱动,在挂起与唤醒中逐步收敛到确定结果。

异步任务不是“一触发就跑完”,而是一条有明确起点、中间状态和终点的事件路径。它不依赖线程持续占用,而是靠状态推进与事件驱动,在资源受限时挂起、条件满足时唤醒,最终收敛到确定结果。
任务源:触发与封装
生命周期始于一个可调度的执行单元——不是函数调用本身,而是它的封装体。在 Python 中是 asyncio.Task 对象,在 Rust 中是 Future 实例,在浏览器中是 Promise 回调或 setTimeout 注册项。这个封装过程完成两件事:捕获当前上下文(变量、引用、所有权),并交由运行时管理。例如:
- 调用
asyncio.create_task(coro)→ 协程被包装为 Task,进入待调度队列 -
tokio::spawn(async { ... })→ 生成JoinHandle,底层创建 Arc并分配 ID -
fetch(url).then(cb)→ 浏览器网络模块接管请求,完成后将 cb 推入微任务队列
排队与调度:等待被选中执行
封装后的任务不会立刻运行,而是进入某类队列等待调度。不同环境使用不同策略:
- Python asyncio:事件循环维护就绪队列,通过
select()或epoll()监听 IO 就绪,再轮询任务状态 - Rust Tokio:工作线程本地队列 + 全局窃取机制,任务首次入队后由调度器决定何时 poll
- 浏览器:宏任务(setTimeout、I/O)与微任务(Promise.then)分离,每轮事件循环先清空微任务队列
此阶段任务处于 Pending 状态,不消耗 CPU,但可能持有内存或文件句柄等资源。
执行与挂起:跨 await 的状态演进
任务真正“动起来”时,并非连续执行,而是在每个 await 点做一次状态切换:
- 调用
poll(),若子 Future 未就绪 → 返回Poll::Pending,注册 waker,自身挂起 - 若就绪 → 返回
Poll::Ready(result),继续执行后续逻辑,或结束 - 每次挂起都会保存当前栈帧中的局部变量(Rust 自动移动,Python 依赖协程对象闭包)
这种“执行–挂起–唤醒–再执行”的循环,就是异步任务区别于同步代码的核心特征。
终止与清理:完成或取消后的收尾
生命周期终点有两种路径,都需显式处理资源:
-
正常完成:返回值被提取(如
handle.await),Drop被调用,释放内部缓冲区、关闭连接、清除临时文件 -
被取消:调用
task.cancel()或handle.abort(),触发CancelledError或panic!,协程/状态机需在try/finally或drop实现中释放资源
注意:取消是协作式的,不是强制杀掉。若任务阻塞在同步 IO 或未响应 waker,可能无法及时退出。











