负责管理组件的优先级、时间切片( Time Slicing )、并发执行( Concurrent Mode ),负责任务调度、优先级管理、中断恢复。
一、 新任务添加流程
实际为 unstable_scheduleCallback 方法执行逻辑。
二、 任务执行流程
taskQueue为空timerQueue为空时,程序处于闲置状态timerQueue不为空- 检出(使用
seek查看)堆顶并设置定时任务
- 检出(使用
taskQueue为不为空- 如果设定定时任务,取消定时任务
- 轮询执行
taskQueue中任务- 检测是否需要让出执行给主线程
- 执行任务
- 根据任务执行的返回值判定是否要刷新任务堆
- 循环执行,直到
- 当前该让出执行
- 当前任务未超时
- 没有可执行任务(
taskQueue为空)timerQueue为空,程序闲置timerQueue为不为空- 堆顶任务已达可执行时间,刷新堆执行
- 堆顶任务未超时,设定异步任务
实际为 requestHostCallback() ➞ schedulePerformWorkUntilDeadline() ➞ performWorkUntilDeadline() ➞ flushWork() ➞ workLoop() ➞ schedulePerformWorkUntilDeadline() 函数调用过程
workLoop() 未直接调用 schedulePerformWorkUntilDeadline() ,而是在 performWorkUntilDeadline() 中执行完 flushWork() 后根据变量判定是否执行 schedulePerformWorkUntilDeadline() ,从而形成外循环
为执行任务的外循环,通过 schedulePerformWorkUntilDeadline() ⇄ performWorkUntilDeadline() 实现。
三、 内循环执行流程
任务堆执行的内循环,通过 while 顺序执行 taskQueue 堆任务。
实际为 workLoop() 函数的执行逻辑
四、定时任务执行流程
在添加任务时若当前执行堆 ( taskQueue ) 为空,且当前添加任务就是定时任务堆( timerQueue ) 的堆顶及执行任务到仅剩下定时任务或执行到任务未超时时都将通过 requestHostTime() 设定 handleTimeout 为执行回调(中的执行方法)。
五、 机制
主要职责:
- 任务调度 : 将高优任务(如用户交互)优先执行
- 中断低优先级任务: 当前执行低优先级任务时,Scheduler 会立即终止其
workLoop(通过shouldYieldToHost返回true),并将剩余任务重新放回队列 - 优先执行高优先级任务:高优先级任务会被立即加入调度列队,并通过
requestHostCallback或requestHostTimeout安排为新的宏任务,从而“夺回”主线程执行权
- 中断低优先级任务: 当前执行低优先级任务时,Scheduler 会立即终止其
- 时间切片 : 在浏览器空闲时执行低优先级任务,避免阻塞主线程
- 协作式调度 : 通过
requestIdleCallback或polyfill实现与浏览器协作,让出控制权 - 优先级管理 : 为不同任务分配不同的优先级(如
Immediate、UserBlocking、Normal、Low、Idle) - 中断条件 : 任务执行超过帧预算( 5ms )或更高级优先任务到达
- 恢复机制 : 任务回调返回一个函数(续体),下次调度时继续执行。Scheduler 会为每一个任务维护状态(如
isCancelled、expirationTime),确保中断的任务在恢复时能从上次停止的位置继续执行- 任务被中断时,记录当前执行待的组件或副作用节点(通过
currentTask的pendingWork指针) - 回复执行时,从该节点继续处理剩余的工作单元 (
performUnitOfWork)
- 任务被中断时,记录当前执行待的组件或副作用节点(通过
- 优先级反转 : 低优先级任务持有高优先级任务所需资源时,可能导致死锁。 React 通过
lanes和lanePriority系统优化依赖关系 - 过期任务处理 : 过期任务会被强制提升到最高优先级执行
- 连续批处理 : 嗯,这个好像是 Fiber 的逻辑
- 与 Fiber 节点协同 : Scheduler 与 Fiber 节点绑定,每个 fiber 对应一个渲染任务,通过 workInProgress 链实现可中断的链式更新
六、 入口
在包的标准入口仅做了模块导出。主逻辑在文件 src/forks/Scheduler
'use strict';
export * from './src/forks/Scheduler';
而相应的在 index.native.js 有同样的逻辑
'use strict';
export * from './src/fork/SchedulerNative';
七、注意的项
1. 时间切片 ≄ requestIdleCallback
虽然理念类似,但 React 自己实现了调度器,不依赖 requestIdleCallback
- 浏览器兼容性差(Safari 长期不支持)
- 在高负载页面中可能长时间不触发
- 无法精准控制优先级
- 在低端设备上表现不佳
- 调试困难
2. 优先级反转问题
- 高优先级任务插入时,会打断当前低优先级任务。但低优先任务不会丢失,只是暂停
- 高优先级的任务依赖低优先级的任务时,需通过
expirationTime动态调整优先级
3. expirationTime 🆚 startTime
startTime: 任务何时“可以开始”(用于延迟任务,如debounce)expirationTime: 任务必须完成的时间(决定优先级)
4. 时间切片依赖宏任务
时间切片的核心是「让出主线程」,必须依赖 宏任务 (如 MessageChannel )实现(在 schedulePerformWorkUntilDeadline() 函数中实现)!!
- 微任务(如
Promise.then)会在当前任务执行结果后立即执行,无法让出主线程给浏览器渲染 - 宏任务执行后,浏览器会优先处理 UI 渲染、用户输入等,再执行下一个宏任务,符合时间切片的需求
- 核心目标 : 通过优先级调度和任务中断,最大化浏览器利用率(60 fps)
- 难点 : 平衡任务公平性(避免低优先级任务饿死)与响应速度
八、 性能
- 最小堆管理任务 :
taskQueue和timerQueue使用最小堆(按优先级/过期时间排序),插入/删除复杂度O(㏒ n) - 避免递归 : 任务拆分为小单元,防止栈溢出(内循环使用数组的形势执行待执行任务,而非递归)
- 内存泄漏 : 及时清理已完成或取消的任务,避免列队中堆积无效任务(但不会直接操作堆,会破坏堆的结构)