react源码解析15.scheduler&Lane

视频解说(高效学习):进入学习

当咱们在相似上面的搜寻框组件进行搜寻时会发现,组件分为搜寻局部和搜寻后果展现列表,咱们冀望输入框能立即响应,后果列表能够有期待的工夫,如果后果列表数据量很大,在进行渲染的时候,咱们又输出了一些文字,因为用户输出事件的优先级是很高的,所以就要进行后果列表的渲染,这就引出了不同工作之间的优先级和调度

Scheduler

咱们晓得如果咱们的利用占用较长的js执行工夫,比方超过了设施一帧的工夫,那么设施的绘制就会出不的景象。

Scheduler次要的性能是工夫切片和调度优先级,react在比照差别的时候会占用肯定的js执行工夫,Scheduler外部借助MessageChannel实现了在浏览器绘制之前指定一个工夫片,如果react在指定工夫内没比照完,Scheduler就会强制交出执行权给浏览器

工夫切片

在浏览器的一帧中js的执行工夫如下

requestIdleCallback是在浏览器重绘重排之后,如果还有闲暇就能够执行的机会,所以为了不影响重绘重排,能够在浏览器在requestIdleCallback中执行耗性能的计算,然而因为requestIdleCallback存在兼容和触发机会不稳固的问题,scheduler中采纳MessageChannel来实现requestIdleCallback,以后环境不反对MessageChannel就采纳setTimeout。

在之前的介绍中咱们晓得在performUnitOfWork之后会执行render阶段和commit阶段,如果在浏览器的一帧中,cup的计算还没实现,就会让出js执行权给浏览器,这个判断在workLoopConcurrent函数中,shouldYield就是用来判断残余的工夫有没有用尽。在源码中每个工夫片时5ms,这个值会依据设施的fps调整。

function workLoopConcurrent() {  while (workInProgress !== null && !shouldYield()) {    performUnitOfWork(workInProgress);  }}
function forceFrameRate(fps) {//计算工夫片  if (fps < 0 || fps > 125) {    console['error'](      'forceFrameRate takes a positive int between 0 and 125, ' +        'forcing frame rates higher than 125 fps is not supported',    );    return;  }  if (fps > 0) {    yieldInterval = Math.floor(1000 / fps);  } else {    yieldInterval = 5;//工夫片默认5ms  }}

工作的暂停

在shouldYield函数中有一段,所以能够晓得,如果以后工夫大于工作开始的工夫+yieldInterval,就打断了工作的进行。

//deadline = currentTime + yieldInterval,deadline是在performWorkUntilDeadline函数中计算出来的if (currentTime >= deadline) {  //...    return true}

调度优先级

在Scheduler中有两个函数能够创立具备优先级的工作

  • runWithPriority:以一个优先级执行callback,如果是同步的工作,优先级就是ImmediateSchedulerPriority

    function unstable_runWithPriority(priorityLevel, eventHandler) {  switch (priorityLevel) {//5种优先级    case ImmediatePriority:    case UserBlockingPriority:    case NormalPriority:    case LowPriority:    case IdlePriority:      break;    default:      priorityLevel = NormalPriority;  }  var previousPriorityLevel = currentPriorityLevel;//保留以后的优先级  currentPriorityLevel = priorityLevel;//priorityLevel赋值给currentPriorityLevel  try {    return eventHandler();//回调函数  } finally {    currentPriorityLevel = previousPriorityLevel;//还原之前的优先级  }}
  • scheduleCallback:以一个优先级注册callback,在适当的机会执行,因为波及过期工夫的计算,所以scheduleCallback比runWithPriority的粒度更细。

    • 在scheduleCallback中优先级意味着过期工夫,优先级越高priorityLevel就越小,过期工夫离以后工夫就越近,var expirationTime = startTime + timeout;例如IMMEDIATE_PRIORITY_TIMEOUT=-1,那var expirationTime = startTime + (-1);就小于以后工夫了,所以要立刻执行。
    • scheduleCallback调度的过程用到了小顶堆,所以咱们能够在O(1)的复杂度找到优先级最高的task,不理解能够查阅材料,在源码中小顶堆寄存着工作,每次peek都能取到离过期工夫最近的task。
    • scheduleCallback中,未过期工作task寄存在timerQueue中,过期工作寄存在taskQueue中。

      新建newTask工作之后,判断newTask是否过期,没过期就退出timerQueue中,如果此时taskQueue中还没有过期工作,timerQueue中离过期工夫最近的task正好是newTask,则设置个定时器,到了过期工夫就退出taskQueue中。

      当timerQueue中有工作,就取出最早过期的工作执行。

function unstable_scheduleCallback(priorityLevel, callback, options) {  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;  }  var timeout;  switch (priorityLevel) {    case ImmediatePriority://优先级越高timeout越小      timeout = IMMEDIATE_PRIORITY_TIMEOUT;//-1      break;    case UserBlockingPriority:      timeout = USER_BLOCKING_PRIORITY_TIMEOUT;//250      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;//优先级越高 过期工夫越小  var newTask = {//新建task    id: taskIdCounter++,    callback//回调函数    priorityLevel,    startTime,//开始工夫    expirationTime,//过期工夫    sortIndex: -1,  };  if (enableProfiling) {    newTask.isQueued = false;  }  if (startTime > currentTime) {//没有过期    newTask.sortIndex = startTime;    push(timerQueue, newTask);//退出timerQueue    //taskQueue中还没有过期工作,timerQueue中离过期工夫最近的task正好是newTask    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {      if (isHostTimeoutScheduled) {        cancelHostTimeout();      } else {        isHostTimeoutScheduled = true;      }      //定时器,到了过期工夫就退出taskQueue中      requestHostTimeout(handleTimeout, startTime - currentTime);    }  } else {    newTask.sortIndex = expirationTime;    push(taskQueue, newTask);//退出taskQueue    if (enableProfiling) {      markTaskStart(newTask, currentTime);      newTask.isQueued = true;    }    if (!isHostCallbackScheduled && !isPerformingWork) {      isHostCallbackScheduled = true;      requestHostCallback(flushWork);//执行过期的工作    }  }  return newTask;}

工作暂停之后怎么持续

在workLoop函数中有这样一段

const continuationCallback = callback(didUserCallbackTimeout);//callback就是调度的callbackcurrentTime = getCurrentTime();if (typeof continuationCallback === 'function') {//判断callback执行之后的返回值类型  currentTask.callback = continuationCallback;//如果是function类型就把又赋值给currentTask.callback  markTaskYield(currentTask, currentTime);} else {  if (enableProfiling) {    markTaskCompleted(currentTask, currentTime);    currentTask.isQueued = false;  }  if (currentTask === peek(taskQueue)) {    pop(taskQueue);//如果是function类型就从taskQueue中删除  }}advanceTimers(currentTime);

在performConcurrentWorkOnRoot函数的结尾有这样一个判断,如果callbackNode等于originalCallbackNode那就复原工作的执行

if (root.callbackNode === originalCallbackNode) {  // The task node scheduled for this root is the same one that's  // currently executed. Need to return a continuation.  return performConcurrentWorkOnRoot.bind(null, root);}

Lane

Lane的和Scheduler是两套优先级机制,相比来说Lane的优先级粒度更细,Lane的意思是车道,相似赛车一样,在task获取优先级时,总是会优先抢内圈的赛道,Lane示意的优先级有以下几个特点。

  • 能够示意不同批次的优先级

    从代码中中能够看到,每个优先级都是个31位二进制数字,1示意该地位能够用,0代表这个地位不能用,从第一个优先级NoLanes到OffscreenLane优先级是升高的,优先级越低1的个数也就越多(赛车较量外圈的车越多),也就是说含多个1的优先级就是同一个批次。

    export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;export const SyncBatchedLane: Lane = /*                 */ 0b0000000000000000000000000000010;export const InputDiscreteHydrationLane: Lane = /*      */ 0b0000000000000000000000000000100;const InputDiscreteLanes: Lanes = /*                    */ 0b0000000000000000000000000011000;const InputContinuousHydrationLane: Lane = /*           */ 0b0000000000000000000000000100000;const InputContinuousLanes: Lanes = /*                  */ 0b0000000000000000000000011000000;export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000100000000;export const DefaultLanes: Lanes = /*                   */ 0b0000000000000000000111000000000;const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000001000000000000;const TransitionLanes: Lanes = /*                       */ 0b0000000001111111110000000000000;const RetryLanes: Lanes = /*                            */ 0b0000011110000000000000000000000;export const SomeRetryLane: Lanes = /*                  */ 0b0000010000000000000000000000000;export const SelectiveHydrationLane: Lane = /*          */ 0b0000100000000000000000000000000;const NonIdleLanes = /*                                 */ 0b0000111111111111111111111111111;export const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;const IdleLanes: Lanes = /*                             */ 0b0110000000000000000000000000000;export const OffscreenLane: Lane = /*                   */ 0b1000000000000000000000000000000;
  • 优先级的计算的性能高

    例如,能够通过二进制按位与来判断a和b代表的lane是否存在交加

    export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane) {  return (a & b) !== NoLanes;}

Lane模型中task是怎么获取优先级的(赛车的初始赛道)

工作获取赛道的形式是从高优先级的lanes开始的,这个过程产生在findUpdateLane函数中,如果高优先级没有可用的lane了就降落到优先级低的lanes中寻找,其中pickArbitraryLane会调用getHighestPriorityLane获取一批lanes中优先级最高的那一位,也就是通过lanes & -lanes获取最左边的一位

export function findUpdateLane(  lanePriority: LanePriority,  wipLanes: Lanes,): Lane {  switch (lanePriority) {    //...    case DefaultLanePriority: {      let lane = pickArbitraryLane(DefaultLanes & ~wipLanes);//找到下一个优先级最高的lane      if (lane === NoLane) {//上一个level的lane都占满了降落到TransitionLanes持续寻找可用的赛道        lane = pickArbitraryLane(TransitionLanes & ~wipLanes);        if (lane === NoLane) {//TransitionLanes也满了          lane = pickArbitraryLane(DefaultLanes);//从DefaultLanes开始找        }      }      return lane;    }  }}

Lane模型中高优先级是怎么插队的(赛车抢赛道)

在Lane模型中如果一个低优先级的工作执行,并且还在调度的时候触发了一个高优先级的工作,则高优先级的工作打断低优先级工作,此时应该先勾销低优先级的工作,因为此时低优先级的工作可能曾经进行了一段时间,Fiber树曾经构建了一部分,所以须要将Fiber树还原,这个过程产生在函数prepareFreshStack中,在这个函数中会初始化曾经构建的Fiber树

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {  const existingCallbackNode = root.callbackNode;//之前曾经调用过的setState的回调  //...    if (existingCallbackNode !== null) {    const existingCallbackPriority = root.callbackPriority;    //新的setState的回调和之前setState的回调优先级相等 则进入batchedUpdate的逻辑    if (existingCallbackPriority === newCallbackPriority) {      return;    }    //两个回调优先级不统一,则被高优先级工作打断,先勾销以后低优先级的工作    cancelCallback(existingCallbackNode);  }    //调度render阶段的终点    newCallbackNode = scheduleCallback(    schedulerPriorityLevel,    performConcurrentWorkOnRoot.bind(null, root),  );    //...}

function prepareFreshStack(root: FiberRoot, lanes: Lanes) {  root.finishedWork = null;  root.finishedLanes = NoLanes;    //...  //workInProgressRoot等变量从新赋值和初始化  workInProgressRoot = root;  workInProgress = createWorkInProgress(root.current, null);  workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;  workInProgressRootExitStatus = RootIncomplete;  workInProgressRootFatalError = null;  workInProgressRootSkippedLanes = NoLanes;  workInProgressRootUpdatedLanes = NoLanes;  workInProgressRootPingedLanes = NoLanes;    //...}

Lane模型中怎么解决饥饿问题(最初一名赛车最初也要达到起点啊)

在调度优先级的过程中,会调用markStarvedLanesAsExpired遍历pendingLanes(未执行的工作蕴含的lane),如果没过期工夫就计算一个过期工夫,如果过期了就退出root.expiredLanes中,而后在下次调用getNextLane函数的时候会优先返回expiredLanes

export function markStarvedLanesAsExpired(  root: FiberRoot,  currentTime: number,): void {  const pendingLanes = root.pendingLanes;  const suspendedLanes = root.suspendedLanes;  const pingedLanes = root.pingedLanes;  const expirationTimes = root.expirationTimes;  let lanes = pendingLanes;  while (lanes > 0) {//遍历lanes    const index = pickArbitraryLaneIndex(lanes);    const lane = 1 << index;    const expirationTime = expirationTimes[index];    if (expirationTime === NoTimestamp) {      if (        (lane & suspendedLanes) === NoLanes ||        (lane & pingedLanes) !== NoLanes      ) {        expirationTimes[index] = computeExpirationTime(lane, currentTime);//计算过期工夫      }    } else if (expirationTime <= currentTime) {//过期了      root.expiredLanes |= lane;//在expiredLanes退出以后遍历到的lane    }    lanes &= ~lane;  }}

export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {     //...  if (expiredLanes !== NoLanes) {    nextLanes = expiredLanes;    nextLanePriority = return_highestLanePriority = SyncLanePriority;//优先返回过期的lane  } else {  //...    }  return nextLanes;}

下图更直观,随之工夫的推移,低优先级的工作被插队,最初也会变成高优先级的工作