React Scheduler 工作原理解析
这份代码是 React 的调度器(Scheduler)实现,主要负责协调和安排各种任务的执行优先级和时机。React 的 Scheduler 是 React 并发模式的核心部分,它允许 React 实现时间切片(time slicing)和优先级调度,使 React 能够中断渲染以响应更高优先级的用户交互。
思维导图:
核心概念
1. 任务优先级
Scheduler 定义了5种优先级:
ImmediatePriority: 最高优先级,需要立即执行UserBlockingPriority: 用户阻塞优先级,如用户输入、点击等交互NormalPriority: 普通优先级,大多数工作的默认优先级LowPriority: 低优先级IdlePriority: 最低优先级,只在浏览器空闲时执行
每种优先级对应不同的超时时间,决定任务可以延迟多久。
2. 任务队列
Scheduler 维护两个主要队列:
taskQueue: 存放已经可以执行的任务timerQueue: 存放延迟执行的任务
这两个队列都是最小堆结构,使得优先级最高的任务总是在堆顶。
关键方法解析
workLoop
workLoop 是调度器的核心循环,负责从任务队列中取出任务并执行:
function workLoop(initialTime) {
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') {
currentTask.callback = null;
currentPriorityLevel = currentTask.priorityLevel;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
// 执行任务回调
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === 'function') {
// 如果回调返回了一个函数,说明任务需要继续执行
currentTask.callback = continuationCallback;
advanceTimers(currentTime);
return true;
} else {
// 任务已完成
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
advanceTimers(currentTime);
}
} else {
pop(taskQueue);
}
currentTask = peek(taskQueue);
if (enableAlwaysYieldScheduler) {
if (currentTask === null || currentTask.expirationTime > currentTime) {
break;
}
}
}
// 返回是否还有更多工作
return currentTask !== null;
}
workLoop 的主要工作:
- 将到期的定时任务从
timerQueue移到taskQueue - 从
taskQueue中取出最高优先级任务执行 - 如果浏览器需要处理其他工作,则中断循环让出控制权
- 如果任务返回一个函数,说明任务需要继续执行,将其放回队列
advanceTimers
function advanceTimers(currentTime) {
// 检查不再延迟的任务并添加到主队列
let timer = peek(timerQueue);
while (timer !== null) {
if (timer.callback === null) {
// 任务被取消
pop(timerQueue);
} else if (timer.startTime <= currentTime) {
// 任务可以执行了,转移到任务队列
pop(timerQueue);
timer.sortIndex = timer.expirationTime;
push(taskQueue, timer);
if (enableProfiling) {
markTaskStart(timer, currentTime);
timer.isQueued = true;
}
} else {
// 剩余计时器仍在等待中
return;
}
timer = peek(timerQueue);
}
}
这个方法检查 timerQueue 中的延迟任务,如果已经到了开始时间,就将其移动到 taskQueue。
unstable_scheduleCallback
function unstable_scheduleCallback(
priorityLevel,
callback,
options,
) {
const currentTime = getCurrentTime();
// 确定任务开始时间
let startTime;
if (typeof options === 'object' && options !== null) {
const delay = options.delay;
if (typeof delay === 'number' && delay > 0) {
startTime = currentTime + delay;
} else {
startTime = currentTime;
}
} else {
startTime = currentTime;
}
// 根据优先级确定超时时间
let 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;
}
const expirationTime = startTime + timeout;
// 创建新任务
const newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
if (startTime > currentTime) {
// 这是一个延迟任务,放入定时器队列
newTask.sortIndex = startTime;
push(timerQueue, newTask);
// 如果这是最早的延迟任务,设置一个超时来处理它
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
if (isHostTimeoutScheduled) {
cancelHostTimeout();
} else {
isHostTimeoutScheduled = true;
}
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
// 这是一个立即执行的任务,放入任务队列
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
// 安排一个主线程回调来执行任务
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback();
}
}
return newTask;
}
这个方法用于安排一个新任务,根据任务的优先级和延迟时间,将其放入不同的队列并安排适当的执行时机。
shouldYieldToHost
function shouldYieldToHost() {
if (!enableAlwaysYieldScheduler && enableRequestPaint && needsPaint) {
// 需要绘制,立即让出控制权
return true;
}
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) {
// 主线程阻塞时间很短,小于一帧,暂不让出
return false;
}
// 让出控制权
return true;
}
这个方法决定是否应该暂停当前工作,让浏览器有机会处理其他任务(如渲染、用户输入等)。它基于两个因素:
- 是否需要绘制(
needsPaint) - 当前任务已经执行了多长时间(是否超过了帧间隔)
调度算法
React Scheduler 的核心调度算法基于以下几个关键点:
1. 优先级队列
使用最小堆数据结构实现优先级队列,确保高优先级任务先执行。任务按照以下方式排序:
- 对于
timerQueue,按照startTime排序 - 对于
taskQueue,按照expirationTime排序
2. 时间切片
Scheduler 实现了时间切片,通过 shouldYieldToHost 方法来决定是否应该中断当前工作。默认情况下,如果执行时间超过了一帧的时间(约16.6ms),就会中断执行,让浏览器有机会处理其他工作。
3. 延续和中断
任务可以返回一个函数,表示需要继续执行。这允许长时间运行的任务被分割成多个较小的部分,每个部分可以在不同的时间片中执行,从而避免阻塞主线程。
4. 平台适配
Scheduler 使用不同的机制来安排回调,优先使用:
setImmediate(如果可用)MessageChannel(大多数现代浏览器)setTimeout(最后的备选方案)
其中 MessageChannel 是最常用的,因为它不受4ms最小超时限制(setTimeout有这个限制)。
总结
React Scheduler 是 React 并发模式的核心部分,允许 React:
- 根据优先级安排不同的工作
- 将长时间运行的工作分割成小块
- 在浏览器需要处理用户交互或渲染时让出控制权
- 在浏览器空闲时继续执行低优先级工作
这种设计使 React 能够保持应用的响应性,即使在进行大量计算工作的情况下,也能及时响应用户交互。