Review
- 2024-09-01 07:34
[!Summary] 基于
scheduler@0.23.0scheduler/src/forks/Scheduler.jsScheduler 作用就是执行回调,把
react-reconciler提供的callback包装到一个任务对象中,在内部维护一个任务队列,优先级高的排在前面。但是低优先级时间已经过期了也会被高优执行,防止饥饿问题。
一、Introduction #
根据 Priority 和 Time Slice 实现
React 实现了一套基于 lane 模型的优先级算法,并基于这套算法实现了 Batched Updates 、任务打断/恢复机制等低级特性。
2种任务队列
- 未过期的:timerQueue,依据
currentTime + delay排序 - 已过期的:taskQueue,依据 task
expirationTime排序,值越小优先级越高
unstable_scheduleCallback ->
requestHostTimeout
requestHostCallback
Scheduler Priority
- NoPriority = 0 // 无优先级
- ImmediatePriority = 1 // 立刻执行,级别最高
- UserBlockingPriority = 2 // 用户阻塞级别的优先级
- NormalPriority = 3 // 正常的优先级
- IdlePriority = 4 // 较低优先级
- LowPriority 5 // 最低优先级
通过小顶堆实现优先级队列:一颗完全二叉树,堆中每一个节点的值都小于等于其子树的每一个值。
根据任务优先级,确定 timeout 时间
// Times out immediately
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
var LOW_PRIORITY_TIMEOUT = 10000;
var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
var timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
var expirationTime = startTime + timeout;调度时机
requestIdleCallback❌ 空闲时间执行,兼容性不好,触发频率不稳定。目的是为了执行低优先级的工作,而不影响高优先级的事件(动画、用户输入)。requestAnimationFrame❌ 每一帧前执行,16ms执行一次,执行频率不高Generator,Async具有传染性,Generator执行的中间状态是上下文关联的setImmediate✅MessageChannel✅setTimeout✅
二、Lane #
Briefly, Lane is a 32-bit mask flag. Each bit represents a priority and a type of task. The closer the lane is to ‘0’, the higher the priority.
React 中的优先级跟事件相关,被称为 EventPriority
- DiscreteEventPriority 离散事件,如 click, focus,
touchstart - ContinuousEventPriority 连续事件,如 drag,
mousemove, scroll - DefaultEventPriority 默认
- IdleEventPriority 空闲情况
需要将 lane 优先级转换为 scheduler 优先级
- 将 lane 转换为 EventPriority,
lanesToEventPriority - 将 EventPriority 转换为 Scheduler 优先级,
switch(lanesToEventPriority(nextLanes))
Lane 优先级定义 #
Lane是32bit Integer,不同优先级对应不同lane,越低的位代表越高额优先级。
| Name | Usage |
|---|---|
NoLane 00000 | Default value |
SyncLane 00001 | ClickEvent, MouseMoveEvent, etc. |
InputContinuousHydrationLane 010 | Unknown. It is probably used for Suspense and hydrateroot |
InputContinuousLane 00100 | MouseEnter, etc. |
DefaultHydrationLane 01000 | Unknown. It is probably used for Suspense and hydrateroot |
DefaultLane 10000 | It is mainly used for initial rendering. |
| TransitionHydrationLane | Unknown. It is probably used for Suspense and hydrateroot |
| TransitionLane1~16 | It is used for startTransition. React uses a different number each time, and if React previously used TransitionLane16, it will use TransitionLane1. |
| RetryLane1~5 | React uses it when Suspense is still loading. |
| IdleHydrationLane | Unknown. It is probably used for Suspense and hydrateroot |
| IdleLane | Unknown |
| OffscreenLane | Unknown. It’s probably used for the offscreen feature, which is not implemented yet. |
Automatic Batching (Batched Updates) #
基于某一个优先级,计算出属于同一批的所有优先级。 将多次更新流程合并为一次处理的技术。
基于
expirationTime的优先级算法,对于高优先级先执行没有问题,但不能很好的支持并发更新,也不能很好的表示批。耦合了“优先级”和“批”两个概念。
基于 Lane 的算法
- 以优先级为依据,对 update 进行排序
- 表达“批”的概念:通过位运算,很容易计算是否属于同一批
Lanes #
lanes 代表一个或多个 lane 的集合
- TransitionLanes
- root.pendingLanes
- root.suspendedLanes
- root.pingedLanes
- root.expiredLanes
- 实现 entangle
- 不依赖优先级划定“批”