Web Worker、任务切分与主线程调度
Web Worker、任务切分与主线程调度
这篇笔记还在编辑中,内容可能会继续补充、调整或重写。
为什么主线程会卡顿
JavaScript、样式计算、布局和大部分绘制工作共享浏览器主线程。一段持续执行的同步任务会阻止输入处理和下一帧绘制,使用户感到点击无响应或滚动卡顿。
优化顺序通常是:先减少不必要工作,再改进算法,然后把剩余工作切成可让出主线程的小任务;只有适合并行且传输成本合理的 CPU 密集任务才移入 Worker。
任务切分
大型循环可以按时间预算分批执行,并在批次之间让浏览器处理输入和渲染。选择 setTimeout、requestAnimationFrame、requestIdleCallback 或调度 API 时,要理解它们分别面向延迟执行、下一帧视觉更新、空闲机会和优先级调度。
微任务不会自动“让出一帧”。递归创建 Promise 微任务仍可能长期占用事件循环,因此不能把代码改成 await Promise.resolve() 就认为完成了任务切分。
Web Worker 的边界
Worker 通过消息与页面通信,不能直接访问 DOM。默认结构化克隆会复制数据;大型 ArrayBuffer 可以转移所有权以减少复制成本。频繁发送大量小消息、重复序列化复杂对象,可能抵消并行带来的收益。
适合 Worker 的场景包括:
- 大型文本解析、压缩或加密
- 图片、音视频和几何计算
- 大数据排序、过滤和聚合
- 不依赖 DOM 的语言服务或编译任务
简单网络请求或少量数组处理通常不值得增加 Worker 生命周期、错误和部署复杂度。
工程化关注点
- 构建工具如何解析
new Worker(new URL('./worker.ts', import.meta.url)) - Worker Chunk 的缓存、CSP 和跨源限制
- 消息协议的类型、版本、取消和错误序列化
- 页面卸载或任务过期时如何终止计算
- SSR 环境中如何避免访问浏览器专属 API
实践任务
实现一个会阻塞主线程的大型过滤任务,分别比较同步执行、分批执行和 Worker:
- 使用 Performance 面板记录长任务和交互延迟。
- 固定数据规模,分别记录总耗时和主线程阻塞时间。
- 加入任务取消,确保旧结果不会覆盖新查询。
- 比较复制普通数组和转移
ArrayBuffer的成本。
验收标准
- 优化前后都有 Performance Trace
- 能区分总计算时间和主线程可响应时间
- 新请求可以取消或废弃旧计算结果
- Worker 错误能被捕获并关联到发布版本
- 数据传输成本被纳入方案比较