1. 概述
React Scheduler 是 React 并发特性的核心调度系统,负责管理不同优先级的任务执行。它通过智能的任务调度和时间切片机制,实现了高性能的用户界面渲染,同时保持了良好的用户体验。
1.1 核心特性
- 优先级调度:支持5级优先级系统
- 时间切片:连续执行时间限制,避免长时间阻塞
- 协作式中断:任务主动让出,支持高优先级任务抢占
- 状态保持:中断后的任务从中断点继续执行
1.2 优先级级别
// 从高到低的优先级
ImmediatePriority // 立即执行
UserBlockingPriority // 用户阻塞优先级
NormalPriority // 正常优先级
LowPriority // 低优先级
IdlePriority // 空闲优先级
2. 核心架构
2.1 数据结构
Task 任务对象
export opaque type Task = {
id: number, // 唯一标识符
callback: Callback | null, // 任务回调函数(可能是延续回调)
priorityLevel: PriorityLevel, // 优先级级别
startTime: number, // 开始时间
expirationTime: number, // 过期时间
sortIndex: number, // 排序索引
isQueued?: boolean, // 是否已入队
};
双队列设计
// 任务队列:存储立即执行的任务
var taskQueue: Array<Task> = []; // 按 sortIndex 排序(最小堆)
// 定时器队列:存储延迟执行的任务
var timerQueue: Array<Task> = []; // 按 startTime 排序
2.2 核心变量
var currentTask = null; // 当前执行的任务
var currentPriorityLevel = NormalPriority; // 当前优先级级别
var isPerformingWork = false; // 是否正在执行工作
var isHostCallbackScheduled = false; // 是否已安排主机回调
var isHostTimeoutScheduled = false; // 是否已安排主机超时
3. 任务中断机制
3.1 中断触发的三个时机
A. 时间片耗尽中断
function shouldYieldToHost(): boolean {
if (!enableAlwaysYieldScheduler && enableRequestPaint && needsPaint) {
return true; // 需要绘制,立即让出
}
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) {
return false; // 时间片未用完,继续执行
}
return true; // 时间片用完,让出控制权
}
工作原理:
- 连续执行时间限制(来自 frameYieldMs 默认时间片长度配置值(5),表示连续执行的最大时间限制,可通过 forceFrameRate 调整)
- 低优先级任务执行时间过长时,时间片耗尽
- 强制让出控制权,允许高优先级任务执行
B. 高优先级任务插入中断
function advanceTimers(currentTime: number) {
let timer = peek(timerQueue);
while (timer !== null) {
if (timer.startTime <= currentTime) {
// 延迟的高优先级任务到期,转移到主队列
pop(timerQueue);
timer.sortIndex = timer.expirationTime; // 设置排序索引为过期时间
push(taskQueue, timer); // 插入主队列
}
}
}
工作原理:
- 高优先级任务可能被延迟执行(使用
delay选项) - 延迟到期时,通过
advanceTimers转移到主队列 - 下次
workLoop执行时,高优先级任务会优先执行 - 注意:高优先级任务插入后不会立即中断当前执行的任务,而是在下次调度循环中优先执行
C. 任务主动让出中断
// 在workLoop中
const continuationCallback = callback(didUserCallbackTimeout);
if (typeof continuationCallback === 'function') {
// 任务返回延续回调,表示需要继续执行
currentTask.callback = continuationCallback;
if (enableProfiling) {
markTaskYield(currentTask, currentTime);
}
advanceTimers(currentTime);
return true; // 立即让出控制权
}
工作原理:
- 长任务可以主动返回延续回调
- 立即让出控制权,允许其他任务执行
- 下次调度时继续执行剩余工作
3.2 中断的具体流程
function workLoop(initialTime: number) {
let currentTime = initialTime;
advanceTimers(currentTime); // 推进定时器,可能将高优先级任务加入队列
currentTask = peek(taskQueue); // 获取当前最高优先级任务
while (currentTask !== null) {
// 检查是否需要让出控制权
if (!enableAlwaysYieldScheduler) {
if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
break; // 退出工作循环,让出给主线程
}
}
// 执行当前任务
const callback = currentTask.callback;
if (typeof callback === 'function') {
const continuationCallback = callback(didUserCallbackTimeout);
if (typeof continuationCallback === 'function') {
// 任务需要继续执行,立即让出
currentTask.callback = continuationCallback;
return true;
} else {
// 任务完成,从队列中移除
pop(taskQueue);
}
}
// 获取下一个任务
currentTask = peek(taskQueue);
}
}
4. 任务恢复机制
4.1 核心机制:延续回调(Continuation Callback)
React Scheduler 通过延续回调机制实现任务从中断点继续执行,而不是重新执行:
// 任务执行过程中主动让出
const continuationCallback = callback(didUserCallbackTimeout);
if (typeof continuationCallback === 'function') {
// 关键:保存延续回调到任务对象中
currentTask.callback = continuationCallback;
if (enableProfiling) {
markTaskYield(currentTask, currentTime); // 标记任务让出
}
// 让出控制权,但任务状态已保存
return true;
}
4.2 任务状态保持
状态保存过程
// 步骤1:任务主动让出(在调度器内部)
function longRunningTask() {
// 执行部分工作...
// 任务返回延续回调,表示需要继续执行
return () => {
// 继续执行剩余工作
return continueRemainingWork();
};
}
// 辅助函数:继续执行剩余工作(概念性示例)
function continueRemainingWork() {
// 继续执行剩余的工作...
return null; // 完成
}
// 步骤2:调度器保存状态
const continuationCallback = callback(didUserCallbackTimeout);
if (typeof continuationCallback === 'function') {
// 保存延续回调,替换原始回调
currentTask.callback = continuationCallback;
// 任务仍在队列中,等待下次调度
}
// 步骤3:下次调度时继续执行
function workLoop(initialTime: number) {
while (currentTask !== null) {
const callback = currentTask.callback; // 获取当前回调
if (typeof callback === 'function') {
// 这个callback可能是:
// 1. 原始任务回调(首次执行)
// 2. 延续回调(恢复执行)
const continuationCallback = callback(didUserCallbackTimeout);
// 继续处理...
}
}
}
5. 源码实现分析
5.1 任务调度入口
function unstable_scheduleCallback(
priorityLevel: PriorityLevel,
callback: Callback,
options?: {delay: number},
): Task {
var currentTime = getCurrentTime();
var startTime;
if (typeof options === 'object' && options !== null) {
var delay = options.delay;
if (typeof delay === 'number' && delay > 0) {
startTime = currentTime + delay;
} else {
startTime = currentTime;
}
} else {
startTime = currentTime;
}
// 根据优先级计算超时时间
var timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = -1;
break;
case UserBlockingPriority:
timeout = userBlockingPriorityTimeout;
break;
case IdlePriority:
timeout = maxSigned31BitInt;
break;
case LowPriority:
timeout = lowPriorityTimeout;
break;
case NormalPriority:
default:
timeout = normalPriorityTimeout;
break;
}
var expirationTime = startTime + timeout;
var newTask: Task = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
if (startTime > currentTime) {
// 延迟任务 → timerQueue
newTask.sortIndex = startTime;
push(timerQueue, newTask);
} else {
// 立即任务 → taskQueue
newTask.sortIndex = expirationTime; // 按优先级排序,过期时间越早优先级越高,最小堆根据sortIndex自动排序
push(taskQueue, newTask);
}
return newTask;
}
5.2 主机环境适配
let schedulePerformWorkUntilDeadline;
if (typeof localSetImmediate === 'function') {
// Node.js 环境:优先使用 setImmediate
schedulePerformWorkUntilDeadline = () => {
localSetImmediate(performWorkUntilDeadline);
};
} else if (typeof MessageChannel !== 'undefined') {
// 现代浏览器:使用 MessageChannel 避免 4ms 限制
const channel = new MessageChannel();
channel.port1.onmessage = performWorkUntilDeadline;
} else {
// 降级方案:使用 setTimeout
schedulePerformWorkUntilDeadline = () => {
localSetTimeout(performWorkUntilDeadline, 0);
};
}
6. 实际应用场景
6.1 React Fiber 中的状态保持
// React 组件渲染任务(概念性示例)
function renderComponent() {
// 渲染部分内容...
// 在 React 内部,会检查是否需要让出
// 如果让出,会返回一个延续回调
return null; // 完成
}
// 调度器处理(在 React 内部)
const continuationCallback = renderComponent();
if (typeof continuationCallback === 'function') {
// 保存延续回调,下次继续执行
// 这是调度器内部逻辑,外部代码不直接操作
}
6.2 用户交互响应场景
// 低优先级:渲染更新
unstable_scheduleCallback(LowPriority, () => {
// 渲染大量 DOM 元素...
// 如果需要让出,返回延续回调
return () => {
// 继续执行剩余工作
return renderRemainingElements();
};
});
// 高优先级:用户点击事件
unstable_scheduleCallback(UserBlockingPriority, () => {
// 立即响应用户点击
});
// 辅助函数:渲染剩余元素
function renderRemainingElements() {
// 继续渲染剩余的元素...
// 如果还需要让出,继续返回延续回调
// 注意:实际使用时应该通过其他方式判断是否需要让出
if (checkTime()) {
return () => renderRemainingElements();
}
return null;
}
// 辅助函数:判断是否需要让出(示例实现)
// 注意:这是一个简化的示例,实际使用时需要更复杂的逻辑
function needToYield() {
return false; // 继续执行
}
// 更实用的示例:使用闭包保存任务开始时间
function createTaskWithTimeCheck() {
const taskStartTime = Date.now(); // 在闭包中保存开始时间
return function needToYield() {
const currentTime = Date.now();
if (currentTime - taskStartTime > 5) { // 5ms 时间片
return true; // 需要让出
}
return false; // 继续执行
};
}
// 使用示例
const checkTime = createTaskWithTimeCheck();
// 在任务中使用 checkTime() 来判断是否需要让出
6.3 复杂计算任务
// 低优先级:复杂计算
unstable_scheduleCallback(LowPriority, () => {
// 执行复杂计算...
// 如果需要让出,保存状态并返回延续回调
if (checkTime()) {
const savedState = getCurrentCalculationState();
return () => continueCalculation(savedState);
}
return null;
});
// 辅助函数:继续计算
function continueCalculation(savedState) {
// 使用保存的状态继续计算...
if (checkTime()) {
const newState = getCurrentCalculationState();
return () => continueCalculation(newState);
}
return null;
}
// 辅助函数:获取当前计算状态
function getCurrentCalculationState() {
// 返回当前计算的状态
return {
progress: 0, // 示例值
data: null, // 示例值
// ... 其他状态信息
};
}
7. 性能优化策略
7.1 性能监控
if (enableProfiling) {
markTaskStart(task, currentTime); // 标记任务开始
markTaskRun(task, currentTime); // 标记任务运行
markTaskYield(task, currentTime); // 标记任务让出
markTaskCompleted(task, currentTime); // 标记任务完成
}
7.2 队列优化
// 使用最小堆实现任务队列,确保 O(log n) 的插入和删除
import {push, pop, peek} from '../SchedulerMinHeap';
// 任务按 sortIndex 排序,高优先级任务优先执行
var taskQueue: Array<Task> = []; // 最小堆实现
8. 总结
8.1 核心优势
- 状态保持:任务执行状态不会丢失
- 高效恢复:从中断点继续,避免重复计算
- 协作式调度:任务主动控制让出时机
- 无缝切换:高优先级任务可以立即执行
8.2 设计原则
- 协作式中断:任务主动让出,而非强制抢占
- 时间切片:连续执行时间限制,确保响应性
- 优先级排序:过期时间决定执行顺序
- 状态保持:被中断的任务保持其执行状态
- 自动恢复:下次调度时继续执行未完成任务
8.3 技术亮点
- 延续回调机制:实现任务状态保持的核心
- 双队列设计:分离立即任务和延迟任务
- 多环境适配:自动选择最佳的主机调度API
- 性能监控:内置性能分析支持
React Scheduler 通过这种精心设计的调度机制,实现了高性能的用户界面渲染,同时保持了良好的用户体验。它是现代前端框架中任务调度的典范,为 React 的并发特性提供了坚实的基础。
本文档基于 React 18+ 版本的 Scheduler 源码分析,源码位置: packages/scheduler/src/forks/Scheduler.js