Effect 深度课effect@4.1.4 · LTS
并发与运行时底层

L20 ★ 结构化并发与异步中断

⏱ 45 min难度 5/5前置:L16、L17
本课目标
  • 区分四种 fork 的生命周期绑定
  • 解释中断如何跨异步边界传播且保证清理
前置课时尚未完成:L16、L17

可以自行跳转学习,但按顺序推进会更省力;学情分析会把这类缺口归入「前置缺口」。

子 fiber 的寿命由谁决定

Effect 采用结构化并发:子 fiber 的生命周期默认绑定父 fiber。四种 fork 策略改变的是「绑定到谁」:forkChild 绑父(自动监督)、forkDetach 绑全局(daemon)、forkScoped 绑局部 scope、forkIn 绑指定 scope。

四种 fork 与中断
← 代码可左右滑动 →
import { Effect, Fiber, Console } from "effect"

const child = Effect.repeat(
  Console.log("tick"),
  Schedule.fixed("1 second")
)

const parent = Effect.gen(function* () {
  yield* Effect.forkChild(child)   // 父结束 → 子被终止
  // Effect.forkDetach(child)      // 父结束/被中断,子仍继续
  // Effect.forkScoped(child)      // 局部 scope 关闭时终止
  yield* Effect.sleep("3 seconds")
})

// 中断并等待 finalizer 执行完
const stop = Effect.gen(function* () {
  const f = yield* Effect.forkChild(parent)
  yield* Effect.sleep("1 second")
  yield* Fiber.interrupt(f)        // 返回 Effect<void>,等待完全终止
  // f.interruptUnsafe()           // fire-and-forget,不等 finalizer
})
💡 Effect.onInterrupt 注册只在中断时触发的清理;Effect.forEach 并发中任一子任务被中断,其余并发任务一起被中断。
🔬 为什么不用轮询中断

轮询标志位需要程序员记得检查,漏一次就永远不响应,且无法与纯函数式组合兼容。Effect 采用完全异步的信号机制:目标 fiber 不轮询,运行时在安全的指令边界注入中断;临界区通过关闭可中断性来保护。中断请求沿 fiber 树向下传播,Scope 的 finalizer 保证资源收尾。

交互演练
关键术语
结构化并发
子任务生命周期绑定父任务,取消与清理可整体传播
异步中断
运行时在安全边界注入取消信号,无需目标轮询
常见误区
× 用 forkDetach 做「顺手起个后台任务」,结果应用退出时它还挂着。
× 误以为 race 的失败者会自动停止——需要确认它已被中断并清理。
随堂测验(即时反馈,答题计入掌握度)
Q1父 fiber 被中断时,forkDetach 出来的 fiber?
Q2Fiber.interrupt 与 interruptUnsafe 的差别?