深入理解 Next.js 的 Turbo Tasks:增量计算缓存系统的架构、原语与用法
【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js
Turbo Tasks(turbo-tasks)是 Turbopack 与 Next.js 底层构建体系(next.js 仓库中turbopack/crates/turbo-tasks)所依赖的增量计算框架,它通过 Rust 宏与类型系统把"缓存、失效与重建"自动化。本文以其在仓库中的官方文档为主体,结合 crate 源码,系统讲解 Turbo Tasks 的 4 个核心原语、任务图与失效传播、增量构建原理,以及#[turbo_tasks::function]宏的属性、签名改写规则、单例模式(Singleton Pattern)与 Vc 读写机制,帮助你理解 Turbopack 为什么能"少做工作却得到正确答案"。
1. Turbo Tasks 是什么:用类型和宏自动化的增量计算
在开始阅读本文之前,你可以先了解 Turbopack 的整体设计:仓库根目录的 readme.md 定位了项目为 "The React Framework",而本模块则位于 turbopack/crates/turbo-tasks。该目录下的 README.md 开门见山地给出定义:
An incremental computation system that uses macros and types to automate the caching process. (一个增量计算系统,使用宏和类型来自动化缓存过程。)
理解这句话的关键在于:一般的缓存方案需要开发者手动决定"缓存什么、何时失效、何时重算",而 Turbo Tasks 尝试把这件事交给编译器(宏)和类型系统。开发者只需要用宏标注函数、用类型声明数据结构,依赖追踪与失效重建就会自动发生。
文档同时建议,想要获得高层概览,可以先阅读官方博客Inside Turbopack: Building Faster by Building Less(博客原文通过文档末尾的[blog-post]链接给出)。本文后续内容以该 crate 文档和源码为准。
四个核心原语(Primitives)
README.md 明确列出 Turbo Tasks 定义的四类基础构造:
| 原语 | 宏/类型 | 职责 |
|---|---|---|
| Functions(函数) | #[turbo_tasks::function] | 执行、失效与重新执行的单元 |
| Values(值) | #[turbo_tasks::value] | 由函数创建、存储并返回的数据 |
| Traits(特质) | #[turbo_tasks::value_trait] | 在值之上定义一组函数的 trait |
| Collectibles(可收集对象) | TurboTasks::emit_collectible | 在函数中发射、沿调用图向上冒泡、可在父函数中收集的值 |
其中 Collectibles 具有一个重要的去重特性:Collectibles 依据 cell id 相等性(cell id equality)进行去重。可以把它理解为"构建过程产生的副作用/告警/错误等带类型的消息",它们在任务调用图内向上冒泡,供上层任务统一收集处理。对应的能力定义在 src/collectibles.rs 的CollectiblesSourcetrait 中,提供了drop_collectibles、take_collectibles、peek_collectibles三种操作。
由原语派生的两个元素
- Tasks(任务):一个函数与其参数的实例。换言之,
#[turbo_tasks::function]函数 + 一组具体参数 = 一个运行时任务,任务是构建系统中工作的最小单位。 Vc(Value Cell,值单元):指向任务中值存储位置的引用。一次函数重执行后,cell 的内容可能因失效而变化;对一个Vc执行读取(.await),会得到该时刻 cell 数据的一个快照引用ReadRef(只读)。
常见设计模式:单例模式
文档末尾专门指出,Turbo Tasks 常用的设计模式是Singleton Pattern(单例模式),其作用是保证"值"与"值 cell"之间的1:1 映射。该模式的完整说明与示例见 singleton_pattern.md,具体内容在第 6 节展开。
2. 函数与任务(Functions and Tasks)
README.md 用一段示意图展示了"一个示例 turbo-task 函数"的形态,随后说明#[turbo_tasks::function]函数是构建过程中的记忆化(memoized)函数。一个函数配合参数构成一个task,任务负责三件事:
- 追踪依赖(Tracking Dependencies):每个任务都会记录它的依赖,用来判断何时需要重算。依赖在一个
Vc<T>被.await时被记录。 - 重算变更(Recomputing Changes):当一个依赖发生变化时,受影响的任务会被自动重算。
- 并行执行(Parallel Execution):每个任务都会被派生为一个 [Tokio task](文档中链接到 Tokio 官方文档的 spawning 教程),跑在 Tokio 多线程 work-stealing 执行器上。
需要强调的一点是:记忆化(memoized)并不是"永久缓存"。它更像是"惰性 + 依赖感知":同一个函数用相同参数调用时复用上次结果,但一旦其读取过的Vc失效,就会自动重新执行。这样保证"缓存自动失效",无需手写版本号或清理逻辑。
任务的外部签名(External Signature)
完整规范位于 function.md。要点如下:
- 定义一个任务,就是在 Rust 函数上标注
#[turbo_tasks::function]并调用它;函数 + 参数的每种唯一组合都会在运行时创建一个新任务。 - 任务函数可以是同步或异步函数。
- 参数必须实现
TaskInputtrait,通常是原始类型(如i32)或包在Vc<T>中的类型。 - 任务函数的外部签名总是返回
Vc<T>(或OperationVc<T>);声明时可以是Vc<T>或ResolvedVc<T>(可再包一层Result<...>),若声明为ResolvedVc<T>,外部签名会被改写回Vc<T>。 - 任务函数不支持泛型(类型参数或生命周期参数)。
#[turbo_tasks::function]宏会改写函数的参数与返回值,改写后的签名称为"外部签名"(external signature)。
参数改写规则:
- 类型为
ResolvedVc<T>的参数改写为Vc<T>:调用函数时会自动 resolve 值 cell,减少在Vc<T>与ResolvedVc<T>之间来回转换的工作量;该规则也适用于嵌套在Option<ResolvedVc<T>>与Vec<ResolvedVc<T>>内的场景(详见FromTaskInputtrait)。 - 方法参数中的
&self被改写为self: Vc<Self>。
返回值改写规则:
Result<Vc<T>>改写为Vc<T>,这样允许在任务函数体内惯用地使用?操作符。ResolvedVc<T>改写为Vc<T>,允许任务直接返回一个已 resolve 的 cell(例如来自ResolvedVc::cell或.resolved_cell()),而无需显式转回Vc<T>;包在Result中的Result<ResolvedVc<T>>同样改写为Vc<T>。- 无返回值的函数改写为返回
Vc<()>。 - 异步函数隐含的
impl Future<Output = Vc<T>>被**压平(flatten)**成Vc<T>类型——Vc<T>实现了IntoFuture,因此可以直接.await。
这些逻辑的一部分由TaskOutputtrait 及其关联类型Return表示(见src/task/下的源码)。
外部签名改写示例(摘自 function.md):
// 声明形式 #[turbo_tasks::function] async fn foo( &self, a: i32, b: Vc<i32>, c: ResolvedVc<i32>, d: Option<Vec<ResolvedVc<i32>>>, ) -> Result<ResolvedVc<i32>> { // ... } // 改写后的外部签名 fn foo( self: Vc<Self>, // 原来是 &self a: i32, b: Vc<i32>, c: Vc<i32>, // 原来是 ResolvedVc<i32> d: Option<Vec<Vc<i32>>>, // 原来是 Option<Vec<ResolvedVc<i32>>> ) -> Vc<i32>; // 原来是 impl Future<Output = Result<ResolvedVc<i32>>>这条规则的价值在于:实现任务的人可以自由选择最方便的签名形式,而调用方看到的始终是一致、友好的Vc<T>世界。
#[turbo_tasks::function]支持的属性(Attributes)
宏可以接受若干可选属性来改变任务行为,多个属性用逗号分隔组合,例如:
#[turbo_tasks::function(fs, session_dependent)] async fn read_file(path: RcStr) -> Result<Vc<FileContent>> { // ... }各属性语义整理如下(依据 function.md):
| 属性 | 含义与约束 |
|---|---|
operation | 把任务标记为operation:外部签名将返回OperationVc<T>而非Vc<T>,且所有参数必须实现OperationValue。Operation 任务是任务图的显式入口,可用于把非响应式代码接入响应式计算图。与&selfreceiver 互斥。 |
root | 把任务标记为聚合图中的根节点。根任务从最大聚合号(u32::MAX)开始,因此位于聚合树顶部;用于表示计算的顶层入口点。 |
fs | I/O 标记:声明任务直接执行文件系统操作。只应标注在直接执行 I/O 的任务上,而不是传递调用它的任务。 |
network | I/O 标记:声明任务直接执行网络操作。与fs一样只标注在直接执行 I/O 的任务上。 |
session_dependent | 标记任务依赖会话(session)。这类任务从持久化缓存恢复时会被重新执行,因为它们依赖外部状态(文件系统、环境变量、网络),而这些状态在不同会话之间可能已经改变。任务完成时不会被标记为"完全干净",而是保留一种特殊的 "session dependent" 脏状态;若之后从持久缓存恢复,该状态会促使它重新执行而不是复用缓存结果。典型用例是:文件读取、环境变量读取、网络请求。 |
session_dependent的示例(摘自文档)清楚地展示了应用位置的原则:
#[turbo_tasks::function(fs, session_dependent)] async fn read(&self, path: FileSystemPath) -> Result<Vc<FileContent>> { // 文件内容在上个会话后可能已变化,因此缓存恢复时始终重新执行。 // ... } #[turbo_tasks::function(session_dependent)] fn read_all_env(&self) -> Vc<TransientEnvMap> { // 环境变量在不同会话之间可能不同。 Vc::cell(self.vars.clone()) }注意:session_dependent应标注在直接读取外部状态的叶子任务上;传递依赖它的任务无需标注——当被依赖的 session-dependent 任务产出新结果时,它们会自然重执行。
方法与self:为值实现方法、为 trait 实现任务
任务可以作为某个值或 trait 实现上的方法存在,依托 Rust 的arbitrary_self_typesnightly 特性(见 function.md 指向 RFC 3519 的链接)。
**固有实现(inherent implementation)**示例:
#[turbo_tasks::value_impl] impl Something { #[turbo_tasks::function] fn method(self: Vc<Self>, a: i32) -> Vc<SomethingElse> { // 收到完整的 Vc<Self>,需要 .await 才能得到 ReadRef<Self>。 let value = self.await?; // Vc 类型适合继续调用声明在 Vc<Self> 上的其他方法: self.method_resolved(a) } #[turbo_tasks::function] fn method_resolved(self: ResolvedVc<Self>, a: i32) -> Vc<SomethingElse> { // 收到 ResolvedVc<Self>,可以 .await 为 ReadRef,也可以(隐式或通过 *)解引用为 Vc。 let value = self.await?; self.method_ref(a) } #[turbo_tasks::function] fn method_ref(&self, a: i32) -> Vc<SomethingElse> { // 便捷形式:收到 self 的已 resolve 版本,读取时无需 .await。 // 可以直接访问结构体字段,但不能调用声明在 Vc<Self> 上的其他方法。 Vc::cell(SomethingElse::new(self.some_field, a)) } }- 声明位置:这些方法定义在
Vc<T>上(即Vc::<Something>::method),而不是内部类型上。 &self语法糖:#[turbo_tasks::function]的&self参数会隐式地从self: Vc<Self>读取值。- 签名改写:所有外部签名改写规则在此同样适用,
self可以是ResolvedVc<T>,也支持async与Result<Vc<T>>返回值。
trait 实现与固有实现几乎相同,只是 trait 中只有外部签名(改写后)需要与 trait 定义对齐:
#[turbo_tasks::value_impl] impl Trait for Something { #[turbo_tasks::function] fn method(self: Vc<Self>, a: i32) -> Vc<SomethingElse> { // self: ResolvedVc<Self> 和 &self 也是合法的参数类型! } }与这些方法/值定义配套的还有#[turbo_tasks::value_trait](用于让 trait 可作为Vc<Box<dyn MyTrait>>存入 cell,方法默认实现可用,支持no_debug、operation等参数)与#[turbo_tasks::value_impl](作用于任何VcValueType的 impl 块),两者的详细文档均以 rustdoc 形式内嵌在 src/lib.rs 中。
3. 任务图(Task Graph)
所有任务及其依赖共同构成一张任务图(task graph)。README.md 用第二张示意图展示了一个任务图的例子,其图注明确说明了这张图表达的三类调用树:
- 初始(冷)执行:一次全新的完整执行;
- "标记脏"(mark dirty)操作:某个文件发生变化时对该任务的标记;
- 从叶子到根部的传播:脏状态如何由叶子逐级传播到根任务。
这张图对**失效传播(invalidation propagation)**至关重要:当一个任务被失效时,变更会沿任务图传播,在必要处触发重建。可以把任务图理解为构建系统的"因果网络"——只要记录下"谁读了谁"的依赖关系,系统就能在源头变化后精准定位哪些下游需要重算。
任务图形态的一个关键注解
从任务的角度,#[turbo_tasks::function]的执行依赖在读取Vc时(即.await时)被记录。这意味着依赖记录不是静态推断,而是动态追踪:同一个任务函数在不同输入下可能读取不同的Vc,任务图也会相应地记录不同的依赖边。这种"按需记录依赖"的机制是 Turbo Tasks 实现精确增量重建的基础。
4. 增量构建(Incremental Builds)
这是本模块的核心价值所在。执行函数时,turbo-tasks会记录读取过哪些Vc;一旦其中任何一个变化,系统就会使由该函数执行所创建的任务失效,任务随后会被调度并重新执行。
自底向上的重建策略
首次执行之后,Turbo Tasks 对增量重建采用**自底向上(bottom-up)**的策略。流程可以概括为:
- 某个源头(例如一个文件)发生变化 → 直接依赖它的任务被标记失效;
- 失效沿任务图传播:只有被影响的任务才会被标记;
- 通过重建失效任务,只有图中受变更影响的部分被重建,未受影响的部分保持原样。
文档中有一句关键总结,值得原样保留:
By rebuilding invalidated tasks, only the parts of the graph affected by changes are rebuilt, leaving untouched parts intact.No work is done for unchanged parts.(通过重建失效任务,只有图中受变更影响的部分被重建,未受影响的部分保持完好。对于未变化的部分不执行任何工作。)
这就是 Turbopack 之所以能够实现"毫秒级热更新"体验的架构根源:它不是在每次变更时全量重算,而是在一张精确的依赖图上执行最小重建。也可以从结构层面印证:任务调度与优先级逻辑集中在 src/manager.rs 的TurboTasks<B: Backend>结构与priority_runner中,后台会持续把失效任务排队、调度,交由 Tokio 多线程执行器处理。
5. 深入 Vc:cell 的创建、读取、更新与执行模型
Vc(Value Cell)是理解整个系统的一把钥匙。turbo-tasks在 src/vc/README.md 中对 cell 的语义做了最系统的阐述,本节将其完整内容与源码互相印证。
5.1 cell 的定义:像电子表格里的单元格
Value cells 表示一个计算的结果(可能是未完成的、pending 的),就像电子表格中的一个单元格。当某个Vc的内容变化时,变化会通过失效依赖任务来传播。
要获得指向值的引用,需要对Vc<T>执行.await得到ReadRef<T>:
let some_vc: Vc<T>; let some_ref: ReadRef<T> = some_vc.await?; some_ref.some_method_on_t();返回的ReadRef<T>是某个时刻 cell 值的一个引用计数快照(基于triomphe::Arc)。这正是文档反复强调的"快照"语义的来源。
5.2 cell 的四个特性
cell 是与任务关联的数据存储位置,提供:
- 不可变性(Immutability):值一旦存入 cell,在该任务重执行之前保持不变。
- 可重算性(Recomputability):如果 cell 被失效或缓存被驱逐,可以通过重执行其所属任务来重新计算内容。
- 依赖追踪(Dependency Tracking):当 cell 的内容被读取(
.await)时,读取任务会被标记为该 cell 的依赖者。 - 持久化(Persistence):由持久化任务拥有的 cell 可以通过
bincodecrate 序列化。
存储模型:cell 存放在"构造它的任务"所关联的数组里。一个Vc既可以指向某个特定 cell(即 "resolved" cell),也可以指向某个函数的返回值(即 "unresolved" cell)。src/vc/README.md 中配图说明:TaskOutput指向一个具体任务及其输出 cell;TaskCell指向任务内的某个具体 cell。Task cell 存储在一张表(概念上等价于Map<ValueTypeId, Vec<SharedReference>>)中,由一个类型 id + 顺序分配的索引对来引用。
5.3 构造 cell
大多数使用#[turbo_tasks::value]宏的类型都会获得一个.cell()方法,该方法返回该类型的Vc。使用#[turbo_tasks::value(transparent)]的透明包装类型无法在包装类型上定义方法,因此需要用Vc::cell函数来构造。
5.4 更新 cell:compare-then-update 与确定性执行
任务每次运行时,其 cell 都会被重建。.cell()或Vc::cell被调用时:
- 该
ValueTypeId的 cell 计数器递增; - 用
PartialEq将新值与上一次执行的值比较; - 若该索引的值不同,cell 被更新,所有依赖任务被失效。
"先比较再更新"(compare-then-update)的行为可以通过#[turbo_tasks::value(cell = "new")]覆盖为"总是更新并失效"。在 src/lib.rs 的#[turbo_tasks::value]宏文档中,对该参数有更细的说明:
cell = "new":总是覆盖 cell 中的值并使所有依赖任务失效。cell = "compare"(默认):覆盖前先与现有值比较,要求类型实现Eq。cell = "keyed":类似"compare",但对透明 map 类型使用逐 key 失效。
因为 cell 由"类型 + 构造顺序"共同定位,任务函数应当保证确定的执行顺序。顺序不一致的函数虽然结果依然正确,但可能因失效额外 cell 而造成无谓的重算。文档给出了两条具体建议:
- 尽量使用行为确定的类型:迭代集合时用
IndexMap、BTreeMap、FrozenMap替代HashMap(后者的迭代顺序是随机的); - 如果在单个 turbo-task 内部并行执行工作,注意不要在跨线程执行的部分里构造 cell(可能导致意外的非确定性行为)。正确的做法是:先在并行中收集结果,回到主线程排序后再构造 cell。
这也解释了为何 src/lib.rs 会导出FxIndexMap、FxIndexSet等基于indexmap、使用FxHasher的确定顺序集合类型——它们是保证 cell 构造顺序确定性的基础设施。
5.5 读取Vc:与普通 Future 的三点差异
Vc实现了IntoFuture,可以.await,但相比普通Future有几个关键差异:
Vc指向的值可能因依赖变化而失效、或被缓存驱逐,因此多次.await同一个Vc可能得到不同结果;ReadRef只是某一时刻 cell 值的快照。- 读取(
.await)Vc会让当前任务被记录为该Vc所属任务或 task cell 的依赖者;当被读的任务/cell 变化时,当前任务可能被重执行。 Vc类型总是Copy的(大多数Future不是)。这得益于Vc内部只是指向 turbo-tasks 框架所管理数据结构的一小组 id 或索引,不做引用计数,并支持为(假设的、未实现的)垃圾回收器做 tracing。
5.6 一致性、执行模型与 Local Output 优化
- 执行模型:虽然任务函数被期望无副作用,但其执行时机仍对性能(或使用 collectibles 表达问题/副作用)很重要。即使未被
.await,未缓存的函数调用也保证在根任务结束前(或包含该调用的任何强一致读取完成前)被执行(可能发出 collectibles)。但其确切开始执行的时间是实现细节;若某依赖失效,函数可能执行不止一次。 - 最终一致性:由于 turbo-tasks 是**最终一致(eventually consistent)**的,连续两次对同一
Vc<T>的.await可能返回不同值。若发生这种情况,任务最终会被失效,并由强一致根任务(OperationVc::read_strongly_consistent)重新执行。顶层任务若尝试对Vc做最终一致性读取会 panic。任务对潜在不一致的值不应 panic(它们可以返回错误,错误会被强一致根任务丢弃)。当前所有不一致任务都会被轮询至完成,未来版本可能在一段时间后丢弃已被判定为不一致的任务。 - Local Outputs 优化:除显式的 "resolved" 与 "operation" 表示外,
Vc还存在第三种内部表示 "LocalVc"(RawVcUnpacked::LocalOutput),它是函数部分参数尚未 resolve时同步返回值的特例,存储在任务本地状态中,待父级非本地任务退出后释放。系统用NonLocalValue标记 trait 与运行时回退检查来防止潜在的本地Vc逃逸出函数生命周期,从而避免在Vc上引入生命周期标注的语法负担。
5.7Vc的三种"子类型"对比
Vc有两个显式"子类型"(都可廉价转回Vc):
ResolvedVc(内部即RawVcUnpacked::TaskCell):对任务内通过Vc::cell/.cell()构造的 cell 的引用。因为 cell 至少被构造过一次,其具体类型已知,支持便宜的下转型(downcast)。存储为 task id + type id + cell id 的组合。OperationVc(内部即RawVcUnpacked::TaskOutput):#[turbo_tasks::function]的同步返回值,内部以 task id 存储;读取前必须先用connect连接。在涉及 collectibles、需要以强一致方式读取函数结果、或使用State时更有用。
三者的能力对比(依据 src/vc/README.md 的表格):
| 类型 | 内部表示 | 相等性 | 下转型 | 强一致性 | Collectibles | 非本地值 |
|---|---|---|---|---|---|---|
Vc | 多种之一(RawVc) | 不可靠(broken) | resolve 后可 | 最终一致 | 不支持 | 不支持 |
ResolvedVc | Task Id + Type Id + Cell Id | 支持 | 支持且便宜 | 最终一致 | 不支持 | 支持 |
OperationVc | Task Id | 支持 | resolve 后可 | 支持 | 支持 | 支持 |
这些表示在内部统一使用类型擦除的RawVc存储,以减少为支持Vc及其子类型所需的单态化膨胀(进而减小二进制体积、缩短编译时间)。这意味着Vc常常与ResolvedVc/OperationVc有相同的内存表示,但因静态类型未固定而不暴露相同方法(如下转型)。
5.8 相等性(Equality)与哈希
由于等价的两个Vc可能拥有不同表示,不推荐直接用相等性比较Vc,而是应当先转换为显式子类型(首选ResolvedVc)再比较。未来版本中Vc可能不再实现Eq、PartialEq或Hash。src/vc/resolved.rs 补充说明:两个ResolvedVc相等意味着二者内存表示完全相同;而Vc相比之下只多了一个潜在表示(可能指向任务本地信息),这正是文档中 "collectibles are deduplicated by cell id equality" 那句论断能够成立的前提。
6. 单例模式(Singleton Pattern):保证值到 Vc 的 1:1 映射
README.md 中提到的单例模式,在 singleton_pattern.md 中有完整说明。
6.1 用途
在 turbo-tasks 的语境里,单例模式用于把一个值 intern 进一个Vc,保证"对于一个值,恰好存在一个已 resolve 的Vc"。这使ResolvedVc的相等性比较更加安全,也适合作为IndexMap或HashMap的键。
6.2 使用步骤
- 把
.cell()方法设为私有:使用#[turbo_tasks::value]而非#[turbo_tasks::value(shared)](后者的shared标志会把宏生成的.cell()方法公开给所有人)。 - 只在唯一的
#[turbo_tasks::function]中调用.cell():该函数扮演构造器角色。 - 构造器参数充当键:构造任务的参数作为键,保证相同值总是在同一任务中 cell 化。
- 只比较
ResolvedVc的相等性:未 resolve 的Vc之间可能不相等。
6.3 完整示例
#[turbo_tasks::value] struct SingletonString { value: String, } #[turbo_tasks::value_impl] impl SingletonString { #[turbo_tasks::function] fn new(value: String) -> Vc<SingletonString> { Self { value }.cell() } } #[test] fn test_singleton() { let a1 = SingletonString::new("a".to_string()).to_resolved().await?; let a2 = SingletonString::new("a".to_string()).to_resolved().await?; let b = SingletonString::new("b".to_string()).to_resolved().await?; assert_eq!(a1, a2); // 相同的 ResolvedVc 相等 assert_ne!(a1, b); let set = HashSet::from([a1, a2, b]); assert_eq!(set.len(), 2); // 只有两个不同的值 }在这个示例中,SingletonString是包装单个String的结构体,new作为其构造器函数,保证相同字符串值始终被 cell 化到同一个任务里——因此相同内容的值会复用同一个已 resolve 的Vc,在作为哈希键、做相等判断时既高效又安全。
7. 小结:读懂 Turbo Tasks 的三个层次
把上面的内容收束起来,可以从三个层次把握 Turbo Tasks:
- 使用层:你写
#[turbo_tasks::function](执行单元)、#[turbo_tasks::value](数据)、#[turbo_tasks::value_trait]/#[turbo_tasks::value_impl](方法)、emit_collectible(副作用/消息),调用函数得到Vc<T>,.await读取得到快照ReadRef<T>。为了正确与高效,需要遵循三条铁律:任务函数无副作用、cell 构造顺序确定、Vc比较前先 resolve。 - 机制层:每次
.await都记录依赖边,全部任务形成任务图;变更触发"标记脏→自底向上传播→最小重建",冷启动后的一切工作都是增量的。 - 语义层:系统是最终一致的(顶层由强一致根任务兜底),cell 更新采用compare-then-update(可用
cell = "new"覆盖),session_dependent等属性用于精确刻画"外部世界状态",持久化缓存通过bincode序列化跨会话复用。
这套文档与 src/vc/README.md、function.md、singleton_pattern.md 及 src/lib.rs 宏文档互为表里。如果你想继续深入,建议从 src/vc/(Vc/ResolvedVc/OperationVc 的实现)、src/manager.rs(TurboTasks调度器)与 src/collectibles.rs(collectibles 语义)读起——它们正是 Turbo Tasks "用宏和类型自动化缓存"这句话的代码级答案。
【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考