react任务调度(一)
任务池
我们知道,react把任务分成了不同优先级,即存在执行任务的先后顺序,所以react分成了2个任务池:
// 立即执行的任务
const taskQueue = []
// 延迟执行的任务
const timerQueue = []
taskQueue为当前正在执行的任务队列。timerQueue为延迟执行的任务队列。
单元任务
一个单独任务的描述:
type Task = {
id: number, // 任务id, 全局递增
callback: Function, // 任务要执行的函数
priority: PriorityLevel, // 任务的优先级
startTime: number, // 任务的开始时间
expirationTime: number, // 任务的过期时间
sortIndex: number, // 任务的排序
}
结合任务池和任务描述,当一个任务进来时,会获取当前时间(performance.now() / new Date())作为startTime.同时会传入一个delay参数。开始时间为:startTime = startTime + delay。如果delay没有值那就是立即执行的任务,当前任务会被推送到taskQueue中。如果有则会被认为是延迟任务,推送到timerQueue中。
调度函数:
type Task = {
id: number, // 任务id, 全局递增
callback: Function, // 任务要执行的函数
priority: PriorityLevel, // 任务的优先级
startTime: number, // 任务的开始时间
expirationTime: number, // 任务的过期时间
sortIndex: number, // 任务的排序
}
let taskIdCounter: number = 0
function ScheduleCallback(
priorityLevel: number, // 任务优先级
callback: Function, // 执行函数
options: { delay: number }
) {
// 确定开始时间和过期时间
let startTime: number
if (option.delay) { // 这里可以对delay类型去做一些判断
startTime = startTime + option.delay
} else {
startTime = performance.now() // 当前时间
}
const expirationTime = startTime + getTimeoutByPriorityLevel(priorityLevel)
// 此刻可以定义一个任务了
const task = {
id: taskIdCounter++,
callback,
priority: priorityLevel,
startTime,
expirationTime,
sortIndex: -1, // 后面再讲解
}
if (option.delay) {
// 延迟任务
} else {
// 立即执行任务
}
}
到这里了,我们来分析一下,立即执行任务要做些什么?
立即执行逻辑:
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
会把当前任务的sortIndex设置成任务的过期时间。当过期时间越近的时候,他的优先级越高。
这里稍微偏离主线说一下,比较任务池中,哪个优先级更高的函数
function compare(a: Node, b: Node) {
// Compare sort index first, then task id.
const diff = a.sortIndex - b.sortIndex;
return diff !== 0 ? diff : a.id - b.id;
}
所以我们知道,当sortIndex越小的优先级越高,当sortIndex相同的则比较他们的任务id了,任务id其实就是任务进入任务池的先后顺序了。
设置完sortIndex以后会把当前的任务推荐立即执行的任务池当中,等待被调用。
然后判断是否当前是否有任务在调用,没有则执行requestHostCallback。我们看下requestHostCallback做了啥?
function requestHostCallback(callback) {
scheduledHostCallback = callback
if (!isMessageLoopRunning) {
isMessageLoopRunning = true
schedulePerformWorkUntilDeadline();
}
}
其中的callback就是flushwork,flushwork就是真正调度任务,后面再详细说怎么去调度任务池中的任务。
schedulePerformWorkUntilDeadline
我们看下schedulePerformWorkUntilDeadline做了啥?
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
// 发送
schedulePerformWorkUntilDeadline = () => {
port.postMessage(null);
};
全局首先会创建MessageChannel实例。然后会创建2个端口,会根据这2个端口来传数据。
发现schedulePerformWorkUntilDeadline其实就是发送了postMessage,其实就是调用了performWorkUntilDeadline:
const performWorkUntilDeadline = () => {
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime();
// Keep track of the start time so we can measure how long the main thread
// has been blocked.
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;
}
};
该函数其实就是执行了scheduledHostCallback,scheduledHostCallback存储的是flushwork。
function flushWork(hasTimeRemaining: boolean, initialTime: number) {
isHostCallbackScheduled = false;
if (isHostTimeoutScheduled) {
isHostTimeoutScheduled = false;
//取消当前的任务
cancelHostTimeout();
}
isPerformingWork = true;
let previousPriorityLevel = currentPriorityLevel;
try {
return workLoop(hasTimeRemaining, initialTime);
} finally {
currentTask = null;
currentPriorityLevel = previousPriorityLevel;
isPerformingWork = false;
}
}
Flushwork会判断当前是否有任务,如果有任务正在执行,会取消当前的任务。执行接下来新的高优先级的任务。 于是会调用了workLoop。 workLoop:在当前时间切片内循环执行任务。
至此我们知道 schedulePerformWorkUntilDeadline 其实就是利用宏任务去执行当前任务池中的任务。那么为什么不直接去执行flush work?如果利用宏任务才能到达目的,为什么不使用setTimeout/requestAnimationFrame呢?
:将在《react任务调度二》分析react的时间切片思想。
转载自:https://juejin.cn/post/7207769757571006522