Fiber调度原理解析

张开发
2026/5/5 12:59:17 15 分钟阅读
Fiber调度原理解析
Fiber 调度原理Scheduler学习笔记一、requestIdleCallback 原理核心作用requestIdleCallback 是浏览器提供的空闲期调度 API其核心能力是在浏览器主线程空闲的时间段内执行回调任务不会阻塞页面的关键渲染流程布局、绘制、用户交互等。浏览器每帧约 16.67ms60fps的执行流程为处理用户事件 → 执行 JS → 布局Layout → 绘制Paint若某一帧完成所有核心工作后仍有剩余时间该时间段即为「空闲期」requestIdleCallback 注册的任务会在此期间执行。关键特性非高优先级空闲期可被新的高优先级任务如用户点击、滚动抢占未执行完的任务会在下一个空闲期继续超时兜底支持传入第二个参数 { timeout: 毫秒数 }若任务在超时时间内仍未被执行浏览器会在主线程繁忙时强制执行避免任务永久挂起空闲时间可控回调函数会接收一个 IdleDeadline 参数通过 deadline.timeRemaining() 可获取当前空闲期剩余时间用于判断任务是否需要中断。局限性React 弃用原生 API 的原因兼容性差IE 完全不支持部分移动端浏览器支持度低触发频率低浏览器在页面闲置时才会频繁触发页面繁忙时可能几秒才触发一次无法满足 React 高频调度需求精度不足时间计算存在偏差无法精准控制任务执行的时间切片。总结requestIdleCallback 是 React Scheduler 的设计灵感来源但 React 基于其核心思想实现了自研的 Scheduler解决了原生 API 的所有问题。二、Scheduler 核心 —— 优先级设计React Scheduler 的核心目标是按优先级调度任务避免低优先级任务阻塞高优先级任务如用户输入、点击比数据请求渲染优先级更高其优先级体系是基于时间的过期策略核心分为「优先级定义」和「调度规则」两部分。优先级核心定义过期时间expirationTimeScheduler 不使用「字符串 / 数字等级」定义优先级如 high/low、1-5而是通过任务的过期时间expirationTime 表征优先级过期时间越短任务越快过期优先级越高过期时间越长任务越晚过期优先级越低已过期的任务当前时间 ≥ 过期时间会被立即执行抢占所有未过期任务的执行权。内置优先级等级从高到低React 为常用场景预设了优先级对应不同的过期时间单位ms核心等级如下实际源码中为常量定义优先级等级过期时间适用场景同步优先级0紧急更新如用户输入、点击高优先级250动画、过渡效果中优先级5000普通 UI 更新如列表渲染低优先级10000非紧急任务如数据预加载空闲优先级Infinity完全空闲时执行如日志上报核心调度规则任务队列按过期时间升序排序高优先级任务排在队首每次仅执行队首的高优先级任务低优先级任务需等待高优先级任务执行完毕执行过程中若有新的更高优先级任务进入队列立即中断当前任务先执行新任务「抢占式调度」核心所有任务均未过期时按「时间切片」执行避免阻塞主线程。三、时间切片Time Slicing实现时间切片是 Scheduler 最核心的实现将长任务拆分为多个可中断的小任务每个小任务执行时间不超过一个「切片时间」剩余任务放到下一个切片执行从而保证主线程不被长期阻塞页面保持流畅。时间切片的核心目标突破 JS 单线程限制无法真正并行通过「分块执行 可中断」模拟并行效果确保每个切片执行时间 ≤ 5msReact 预设远小于浏览器一帧 16.67ms浏览器有足够时间处理一帧的核心工作布局、绘制、用户交互任务执行过程可被高优先级任务抢占无卡顿。核心实现原理替代原生 requestIdleCallbackReact 自研了基于 requestAnimationFrame MessageChannel 的空闲期检测机制解决原生 API 缺陷核心流程如下通过 requestAnimationFrame 获取每帧的开始时间计算出当前帧的剩余可用时间16.67ms - 已执行时间使用 MessageChannel 创建微任务级别的调度通道将任务放到 MessageChannel 的回调中执行优先级高于宏任务避免任务延迟执行任务前做「剩余时间检测」通过 performance.now() 计算当前切片已执行时间若超过预设切片时间5ms立即中断任务中断后将剩余任务重新加入任务队列等待下一个切片时间继续执行若执行过程中检测到高优先级任务或浏览器无空闲时间立即中断优先处理主线程核心工作。核心实现要点可中断任务执行过程中无全局锁通过「剩余时间检测」主动中断而非被动等待无阻塞每个切片执行时间极短浏览器有足够时间处理一帧的所有核心工作抢占式中断后高优先级任务可插队保证用户交互等紧急操作的响应速度兼容性强基于浏览器通用 APIrequestAnimationFrame、MessageChannel无兼容性短板。四、模拟实现时间切片Time Slicing实现目标模拟 React Scheduler 核心的时间切片能力实现长任务自动拆分为小任务每个小任务执行时间 ≤ 5ms任务执行过程可被中断剩余任务自动续跑不阻塞主线程页面可正常响应用户交互。代码实现/** * 模拟 React Scheduler 时间切片实现 * 核心分块执行长任务 剩余时间检测 可中断 */classTimeSlicingScheduler{constructor(){this.taskQueue[];// 任务队列this.isRunningfalse;// 是否正在执行任务避免重复调度this.timeSlice5;// 切片时间默认5ms同React}/** * 添加任务到队列 * param {Function} task - 要执行的任务需是可分块的迭代器函数 * param {number} priority - 优先级数字越小优先级越高 */addTask(task,priority10){this.taskQueue.push({task:this.wrapTask(task),// 包装为迭代器支持分块执行priority,startTime:performance.now(),});// 按优先级升序排序高优先级在前this.taskQueue.sort((a,b)a.priority-b.priority);// 启动调度this.schedule();}/** * 将普通函数包装为迭代器支持分块执行核心可中断 * param {Function} task - 原始长任务 * returns {Generator} 迭代器对象 */wrapTask(task){returnfunction*(){yieldtask();// 分块执行支持中断后续跑}();}/** * 核心调度方法时间切片执行任务 */schedule(){// 若已有任务在执行直接返回避免重复执行if(this.isRunning)return;this.isRunningtrue;// 启动任务执行使用 requestAnimationFrame 对齐浏览器帧constframeCallback(timestamp){// 执行任务直到切片时间用尽或任务队列为空consthasMoreTasksthis.executeTasks(timestamp);if(hasMoreTasks){// 还有剩余任务继续调度下一帧requestAnimationFrame(frameCallback);}else{// 任务执行完毕重置状态this.isRunningfalse;}};requestAnimationFrame(frameCallback);}/** * 执行任务核心逻辑剩余时间检测 分块执行 * param {number} startTime - 当前帧开始时间 * returns {boolean} 是否还有剩余任务 */executeTasks(startTime){letcurrentTaskthis.taskQueue[0];if(!currentTask)returnfalse;const{task}currentTask;letshouldContinuetrue;// 循环执行直到切片时间用尽或任务执行完毕while(shouldContinuecurrentTask){// 检测剩余时间当前时间 - 帧开始时间 切片时间 → 中断constelapsedTimeperformance.now()-startTime;if(elapsedTimethis.timeSlice){shouldContinuefalse;// 切片时间用尽中断break;}// 执行当前任务的一个小切片迭代器nextconstresulttask.next();// 若任务执行完毕迭代器done从队列中移除if(result.done){this.taskQueue.shift();}// 更新当前任务队列首元素currentTaskthis.taskQueue[0];}// 返回是否还有剩余任务队列非空 或 当前任务未执行完returnthis.taskQueue.length0||(currentTask!shouldContinue);}}// ---------------------- 测试用例 ----------------------// 1. 初始化调度器constschedulernewTimeSlicingScheduler();// 2. 模拟一个长任务循环10000次打印计数正常执行会阻塞主线程functionlongTask(){letcount0;return(){// 每次切片执行100次分100块执行避免单次阻塞for(leti0;i100;i){count;if(count%10000){console.log(长任务执行中${count}/10000);}}// 任务未执行完时继续返回执行if(count10000){returnfalse;}console.log(长任务执行完毕);returntrue;};}// 3. 添加长任务到调度器优先级10scheduler.addTask(longTask(),10);// 4. 添加高优先级任务优先级1会插队执行scheduler.addTask((){console.log(【高优先级任务】执行用户点击事件处理);returntrue;},1);// 测试页面点击事件验证不阻塞document.addEventListener(click,(){console.log(页面点击响应无卡顿主线程未被阻塞);});代码核心说明迭代器包装wrapTask将长任务包装为 Generator 迭代器通过 task.next() 实现分块执行这是任务可中断的核心中断后下次执行从上次的 next() 继续优先级排序添加任务时按优先级升序排序保证高优先级任务始终在队首执行帧对齐requestAnimationFrame让任务执行与浏览器帧同步避免浪费空闲时间剩余时间检测通过 performance.now() 计算已执行时间超过 5ms 立即中断保证主线程空闲无阻塞验证测试用例中添加了页面点击事件执行长任务时点击页面可正常响应无卡顿。运行效果高优先级任务先执行打印「【高优先级任务】执行用户点击事件处理」长任务分块执行每次打印「长任务执行中1000/10000」「2000/10000」…每块执行时间 ≤5ms页面交互正常执行过程中点击页面立即打印「页面点击响应无卡顿主线程未被阻塞」任务执行完毕最后打印「长任务执行完毕」无任何阻塞。五、Scheduler 与 Fiber 架构的关联Scheduler 是 React Fiber 架构的调度层核心为 Fiber 树的构建和更新提供底层支持Fiber 树的调和Reconciliation过程被拆分为多个小任务由 Scheduler 按时间切片执行Fiber 节点的优先级与 Scheduler 任务优先级一致保证高优先级的 Fiber 更新如用户交互可抢占低优先级更新Scheduler 的抢占式调度是 Fiber 「可中断调和」的基础调和过程中可随时中断跳过高优先级任务的调和调和过程中若时间切片用尽或有高优先级任务Scheduler 会中断执行将剩余工作交给下一个切片保证页面流畅。六、核心知识点总结requestIdleCallback 是 Scheduler 的设计灵感但其兼容性和触发频率问题导致 React 自研调度机制Scheduler 优先级基于过期时间过期时间越短优先级越高支持抢占式调度时间切片的核心是长任务分块 可中断 剩余时间检测通过 requestAnimationFrame MessageChannel 实现时间切片的关键是主动中断而非被动等待保证主线程不被阻塞Scheduler 是 Fiber 架构的底层支撑为 Fiber 调和提供优先级调度和时间切片能力。

更多文章