Scheduler任务调度执行
# 概览
Scheduler调度器的任务调度,经过MessageChannel或者定时器,会触发performWorkUntilDeadline方法。本文主要介绍任务调度的执行。
# 源码分析
# performWorkUntilDeadline
performWorkUnitDeadline函数会在每一帧的时间分片内执行工作。
function performWorkUntilDeadline() {
// 重置是否需要绘制的标志,这个标志用于在浏览器绘制之前让出主线程
needsPaint = false;
// 确保调度循环正在运行,在requestHostCallback中置为true
if (isMessageLoopRunning) {
// 获取当前时间
const currentTime = getCurrentTime();
// 记录开始时间
startTime = currentTime;
// 默认还有更多任务需要处理
let hasMoreWork = true;
try {
// 调用flushWork,得到一个布尔值,表示是否还有更多的工作
hasMoreWork = flushWork(currentTime);
} finally {
// 如果还有更多任务,继续调用schedulePerformWorkUntilDeadline方法,调度任务在下一帧中执行
if (hasMoreWork) {
schedulePerformWorkUntilDeadline();
} else {
// 否则停止调度循环,重置标志位
isMessageLoopRunning = false;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# flushWork
flushWork用于设置和清理环境,并调用workLoop
function flushWork(initialTime) {
// 重置调度标志变量,表示已经调度了
isHostCallbackScheduled = false;
// 判断之前是否设置了延迟任务的超时,若设置过,则取消定时器,以及重置标志位
if (isHostTimeoutScheduled) {
isHostTimeoutScheduled = false;
cancelHostTimeout();
}
// 标记正在执行工作
isPerformingWork = true;
// 保存之前的优先级,以便恢复
const previousPriorityLevel = currentPriorityLevel;
try {
return workLoop(initialTime);
} finally {
// 清理工作
currentTask = null;// 清空当前任务
currentPriorityLevel = previousPriorityLevel;//恢复之前的优先级
isPerformingWork = false;//标记工作结束
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# workLoop
workLoop就是一个循环处理任务队列,会从任务队列中取出任务并执行,直到满足退出条件(任务执行完成或应该让出主线程)
function workLoop(initialTime) {
let currentTime = initialTime;
// 调用 advanceTimers方法,将定时器队列中到期的任务加入到任务队列中
advanceTimers(currentTime);
// 取出任务队列中的优先级最高的任务
currentTask = peek(taskQueue);
// 循环执行任务
while (currentTask !== null) {
// 如果当前任务的过期时间大于当前时间,说明还没有过期,此时就需要检查是否应该让出主线程
// 如果当前任务已经过期,那么即使此时应该让出主线程,也需要继续执行,因为过期任务必须完成
// 因此,只有当当前任务没有过期,且应该让出主线程时,才跳出循环
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);
// 返回 true,表示还有工作,继续调度,执行下次循环
return true;
} else {
// 如果回调没有返回函数,则说明任务已经完成,需要从队列中移除
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
// 继续检查定时器队列
advanceTimers(currentTime);
}
} else {
// 如果回调不是函数,则说明任务已经完成或者被取消了,直接移除
pop(taskQueue);
}
// 继续从任务队列中取出下一个任务
currentTask = peek(taskQueue);
}
// 如果任务队列中还有任务,说明是因为让出主线程而退出的循环,返回true,表示还有工作
if (currentTask !== null) {
return true;
} else {
// 如果任务队列为空,则检查定时器队列
const firstTimer = peek(timerQueue);
if (firstTimer != null) {
// 如定时器队列中还有任务,就设置一个超时,在定时器任务开始时间到达时执行
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
// 返回 false,表示没有更多工作
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# shouldYieldToHost
shouldYieldToHost函数返回一个布尔值,表示是否应该让出主线程。
const frameInterval = 5; // 5ms
function shouldYieldToHost() {
// 若需要渲染,则返回true,否则判断时间差,任务调度执行的时间是否小于5ms,若小于,则返回false;否则返回true
return needsPaint
? true
: getCurrentTime() - startTime < frameInterval
? false
: true;
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 关键逻辑解析
# 中断机制
if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
break;
}
1
2
3
2
3
# 条件解释
currentTask.expirationTime > currentTime:任务还未到期shouldYieldToHost():返回一个布尔值,表示是否需要让出主线程(时间片用完了)
# 中断场景
- 高优先级更新任务到来
- 用户交互(如点击)
- 浏览器需要重绘
- 时间片用尽(默认5ms)
# 任务执行和续传
const continuationCallback = callback(didUserCallbackTimeout);
if (typeof continuationCallback === "function") {
currentTask.callback = continuationCallback;
return true;
}
1
2
3
4
5
2
3
4
5
续传模式:这是Fiber架构的关键,让React可以将渲染工作分为多个小任务
// React组件的渲染可能返回续传函数
function renderComponent(/* ... */) {
// 执行一部分工作...
if (/* 还有工作 */) {
return renderComponent; // 返回自身,表示需要继续
}
return null; // 返回null,表示完成
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 两个队列
任务队列(taskQueue)和定时器队列(timerQueue)。定时器队列中的任务还没有到开始时间,当时间到达时,advanceTimers会将它们移到任务队列中。
# 超时处理
如果当前没有立即需要执行的任务,但是定时器队列中有未来的任务,那么会通过requestHostTimeout设置一个超时,在任务开始时间到达时再执行
# 总结
本文中的函数是Scheduler调度器的核心功能,共同实现了时间分片,任务可中断/恢复,基于优先级调度,延迟任务处理和异常安全的执行。
编辑 (opens new window)
上次更新: 2026/02/05, 09:03:30