Effect.all 默认是并发执行的吗?
三个任务放进 Effect.all([taskA, taskB, taskC]),看起来像是并发跑,实际上是一个接一个跑。
不是。Effect.all 和 Effect.forEach 默认是顺序执行,要并发必须显式写 concurrency;而一旦写,就要给一个有理由的数字,别用 "unbounded" 图省事。
这个问题有两个方向相反的坑:以为并发其实是顺序,和并发过了头。再加上一个相关的坑:把任务丢到后台就不管了。三个都围绕同一件事,你到底有没有显式地决定并发行为。并发的完整讲法见 并发。
不写 concurrency 为什么是顺序?
// 以为是并发,其实是顺序
Effect.all([taskA, taskB, taskC])Effect.all 与 Effect.forEach 默认是顺序执行。不加 concurrency 选项时,你并没有得到并发。想要并发,就把并发度写出来。
"unbounded" 有什么问题?
// 对一万个元素无上限并发
Effect.forEach(ids, fetchOne, { concurrency: "unbounded" })"unbounded" 把所有任务同时放出去。对下游服务或连接池来说,这就是一场自己发起的压力测试。
正确方向是显式写出并发度,并且让这个数字有理由,比如下游能承受多少、连接池有多大。"unbounded" 只在任务数量本身就有上限时才说得通。
forkDetach 把任务丢到后台有什么风险?
用 forkDetach 把任务丢到后台、再也不管它,会让它脱离父 fiber 的生命周期,失去结构化并发的保护。fiber 是 Effect 里的并发执行单元,父子关系是结构化并发的基础,详见 Fiber。
除非你真的需要独立生命周期,否则用 forkChild,或者把任务放进 Scope。更多的 fork 选项见 fork 选项,并发与资源的整体讲法见 并发与资源。
版本是 effect 4.0.5。本文的代码和结论都是示意,没有逐条运行验证。