react的调度机制是指react任务执行的优先级调配,如果连续有任务ABC,但C的任务优先级最高,那么先优先执行,通过源码来看看是怎么调度的。
调度机制的核心函数代码是:
export let requestHostCallback;
export let cancelHostCallback;
export let requestHostTimeout;
export let cancelHostTimeout;
export let shouldYieldToHost;
export let requestPaint;
export let getCurrentTime;
export let forceFrameRate;
在Scheduler.js文件中有
const localSetImmediate =
typeof setImmediate !== 'undefined' ? setImmediate : null; // IE and Node.js + jsdom
let schedulePerformWorkUntilDeadline;
// 判断是否有setImmediate 一般是node环境
if (typeof localSetImmediate === 'function') {
schedulePerformWorkUntilDeadline = () => {
localSetImmediate(performWorkUntilDeadline);
};
// 判断浏览器是否支持MessageChannel机制,具体可以查阅mdn:
// <https://developer.mozilla.org/zh-CN/docs/Web/API/MessageChannel>
} else if (typeof MessageChannel !== 'undefined') {
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
schedulePerformWorkUntilDeadline = () => {
port.postMessage(null);
};
} else {
schedulePerformWorkUntilDeadline = () => {
localSetTimeout(performWorkUntilDeadline, 0);
};
}
然后来看下这个performWorkUntilDeadline函数的实现,执行之前会记录当前时间,这是便于记录占用线程的时间,来调度任务,避免占用线程时间太久。如果还有当前任务要执行,继续回调schedulePerformWorkUntilDeadline。
const performWorkUntilDeadline = () => {
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime();
// 记录执行前的时间,主要是要判断占用线程多久的时间,如果占用时间太长且有优先级更高的,要执行优先级高的
startTime = currentTime;
const hasTimeRemaining = true;
// 是否还有要执行的
let hasMoreWork = true;
try {
hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
} finally {
if (hasMoreWork) {
// 调度一次完后还有要执行的再回调执行
schedulePerformWorkUntilDeadline();
} else {
isMessageLoopRunning = false;
scheduledHostCallback = null;
}
}
} else {
isMessageLoopRunning = false;
}
needsPaint = false;
};
requestHostCallback
function requestHostCallback(callback) {
scheduledHostCallback = callback;
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
schedulePerformWorkUntilDeadline();
}
}
看到这里应该大致清楚这个调用的过程
- 创建messageChannel,绑定message事件
- 调用 requestHostCallback 设置 调度callback,然后执行设置isMessageLoopRunning表示当前线程正在执行,然后调用schedulePerformWorkUntilDeadline
- schedulePerformWorkUntilDeadline中通过postMessage,channel port在监听message事件汇后,调用performWorkUntilDeadline
- performWorkUntilDeadline 函数中记录调用的时间,然后调用scheduledHostCallback,执行任务,执行完后如果还有work,继续进行
所以这会我们只需要找到调用requestHostCallback函数的地方,总共有三处
- handleTimeout
- unstable_scheduleCallback
- unstable_continueExecution
先从handleTimeout和unstable_scheduleCallback入手
unstable_scheduleCallback
function unstable_scheduleCallback(priorityLevel, callback, options) {
var timeout;
// 开始时间
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;
}
// 根据优先级,设置调度的总时间,超过时间就要考虑调整了
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT; // -1
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT; // 250
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT; // maxSigned31BitInt
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT; // 10000
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT; // 5000
break;
}
// 过期时间 deadline
var expirationTime = startTime + timeout;
// 创建task对象
var newTask = {
id: taskIdCounter++,
callback, // 执行的callback
priorityLevel, // 执行优先级
startTime, // 开始时间
expirationTime, // 过期时间,这个跟优先级对应的,越小说明任务越急
sortIndex: -1,
};
if (enableProfiling) {
newTask.isQueued = false;
}
// 如果任务开始时间大于当前时间,说明这个目前还不用执行,push到timerQueue里
if (startTime > currentTime) {
// This is a delayed task.
newTask.sortIndex = startTime;
push(timerQueue, newTask);
// taskQueue peek 为null,说明目前没有要执行的task了,然后timerQueue 同时peek 为当前的task
// 说明当前task应该是最早执行的,调用requestHostTimeout
/**
function requestHostTimeout(callback, ms) {
taskTimeoutID = localSetTimeout(() => {
callback(getCurrentTime());
}, ms);
}
*/
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
if (isHostTimeoutScheduled) {
cancelHostTimeout();
} else {
isHostTimeoutScheduled = true;
}
// 通过settimeout调度执行
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
// 从上面逻辑看这里处于ImmediatePriority级别,push进taskQueue进行执行
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
if (enableProfiling) {
markTaskStart(newTask, currentTime);
newTask.isQueued = true;
}
// 没有被执行过久调用requestHostCallback执行
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
return newTask;
}
这里出现了两个队列,taskQueue 和 timerQueue。一个是任务队列一个是时间延时队列,push进taskQueue表面应该在下次workloop过程中就必须拿出来执行,timerQueue则表示延时的,其实就是优先级还没那么高的。workloop的逻辑在flushWork,接下来看flushWork的逻辑。
FlushWork
function flushWork(hasTimeRemaining, initialTime) {
if (enableProfiling) {
markSchedulerUnsuspended(initialTime);
}
isHostCallbackScheduled = false;
if (isHostTimeoutScheduled) {
isHostTimeoutScheduled = false;
cancelHostTimeout();
}
isPerformingWork = true;
const previousPriorityLevel = currentPriorityLevel;
try {
if (enableProfiling) {
try {
return workLoop(hasTimeRemaining, initialTime);
} catch (error) {
if (currentTask !== null) {
const currentTime = getCurrentTime();
markTaskErrored(currentTask, currentTime);
currentTask.isQueued = false;
}
throw error;
}
} else {
return workLoop(hasTimeRemaining, initialTime);
}
} finally {
currentTask = null;
currentPriorityLevel = previousPriorityLevel;
isPerformingWork = false;
if (enableProfiling) {
const currentTime = getCurrentTime();
markSchedulerSuspended(currentTime);
}
}
}
workLoop
function workLoop(hasTimeRemaining, initialTime) {
let currentTime = initialTime;
advanceTimers(currentTime);
currentTask = peek(taskQueue);
// while 循环 说明目前有task要执行
while (
currentTask !== null &&
!(enableSchedulerDebugging && isSchedulerPaused)
) {
if (
// 1. 过期时间大于当前时间 说明可以延后执行
// 2. 没有时间剩余或者能够让步输入相关的更新
currentTask.expirationTime > currentTime &&
(!hasTimeRemaining || shouldYieldToHost())
) {
break;
}
const callback = currentTask.callback;
if (typeof callback === 'function') {
currentTask.callback = null;
currentPriorityLevel = currentTask.priorityLevel;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
if (enableProfiling) {
markTaskRun(currentTask, currentTime);
}
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === 'function') {
// 还有继续要执行的callback,赋值给currentTask.callback
currentTask.callback = continuationCallback;
if (enableProfiling) {
markTaskYield(currentTask, currentTime);
}
} else {
if (enableProfiling) {
markTaskCompleted(currentTask, currentTime);
currentTask.isQueued = false;
}
// 没有的话从队列去移出
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
advanceTimers(currentTime);
} else {
pop(taskQueue);
}
// 执行完将顶部task赋值给currentTask 继续执行
currentTask = peek(taskQueue);
}
//
if (currentTask !== null) {
return true;
} else {
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
return false;
}
}
一些概念
时间切片
在workLoop的while循环中,每次执行task之前都会判断是否出现超时的现象
可中断
workloop中的这段代码其实也是可中断的意思,把continuationCallback赋值给currentTask,然后标记yield,表示中断
currentTask.callback = continuationCallback;
if (enableProfiling) {
markTaskYield(currentTask, currentTime);
}
shouldYield 逻辑
function shouldYieldToHost() {
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) {
// 时间间隔小于react定义的一帧时间:5ms,不用暂停
return false;
}
if (enableIsInputPending) {
if (needsPaint) {
// There's a pending paint (signaled by `requestPaint`). Yield now.
return true;
}
// 判断用户是否有输入,这里这个输入可能不单只输入框,也可以是mousemove、keydown等
// react的实现是通过浏览器的navigator.scheduling.isInputPending事件来判断
// 如果有输入事件判断为true
// 这里有两个事件,一个是50ms、300ms,如果用户浏览器支持判断输入事件,那么会用isInputPending来判断,否则最大的时间就是300ms
if (timeElapsed < continuousInputInterval) {
if (isInputPending !== null) {
return isInputPending();
}
} else if (timeElapsed < maxInterval) {
if (isInputPending !== null) {
return isInputPending(continuousOptions);
}
} else {
// 占据线程时间太长了 直接pass 直接yield
return true;
}
}
return true;
}
这个方法在调和和调度都用
- 在调度中workLoop,判断task是否能执行中使用
// while 循环 说明目前有task要执行
while (
currentTask !== null &&
!(enableSchedulerDebugging && isSchedulerPaused)
) {
if (
currentTask.expirationTime > currentTime &&
(!hasTimeRemaining || shouldYieldToHost())
) {
break;
}
...
}
- 在调和中workLoopConcurrent,也有shouldYield的判断,如果当前是停止的状态,禁止进入调和阶段,当然这个是个while循环,很快就又会执行
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
而在workLoopConcurrent调用链的上层是performConcurrentWorkOnRoot,其中是通过shouldTimeSlice来判断是否进入workLoopConcurrent。
const shouldTimeSlice =
!includesBlockingLane(root, lanes) &&
!includesExpiredLane(root, lanes) &&
(disableSchedulerTimeoutInWorkLoop || !didTimeout);
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
💡安利
- React Internals Explorer:外网一位哥们开发的网站,我觉得很有趣,是一个可视化的react fiber,包括了更新过程的动态图示,很方便理解。可以自己在编辑器上写些代码,fork一份出来就能修改了。