[React 源码] React 18.2 - 饥饿问题 [0.7k 字 - 阅读时长2min]

460 阅读2分钟

什么是饥饿问题: 高优先级一直打断低优先级,低优先级没有机会执行。

如何解决饥饿问题:

第一步: 在每次调度更新都会执行的 ensureRootIsScheduled 调用 markStarvedLanesAsExpired 函数。

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  const existingCallbackNode = root.callbackNode;

  // Check if any lanes are being starved by other work. If so, mark them as
  // expired so we know to work on those next.
  markStarvedLanesAsExpired(root, currentTime);

第二步: 在 markStarvedLanesAsExpired 函数,如果是第一次更新,那会遍历 root.pendingLane 上的 每一个 lane,针对于不同 lane 上的任务,通过调用 computeExpirationTime 为每一个任务计算过期时间。


function computeExpirationTime(lane: Lane, currentTime: number) {
  switch (lane) {
    case SyncHydrationLane:
    case SyncLane:
    case InputContinuousHydrationLane:
    case InputContinuousLane:
      return currentTime + 250;
    case DefaultHydrationLane:
    case DefaultLane:
    case TransitionHydrationLane:
    case TransitionLane1:
    case TransitionLane16:
      return currentTime + 5000;
    case RetryLane4:
      return NoTimestamp;
    case SelectiveHydrationLane:
    case IdleHydrationLane:
    case IdleLane:
    case OffscreenLane:
      return NoTimestamp;
  }
}

第三步: 下次调度更新再次来到markStarvedLanesAsExpired 函数,仍然去遍历每一条 lane, 对于新的更新任务还是计算过期时间,而对于哪些已经有过期时间的任务,去判断它们是否已经到期。如果已经到期 在 root.expiredLanes 上标记已经过期的 lane

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 & ~RetryLanes;
  while (lanes > 0) {
    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;
    }

    lanes &= ~lane;
  }
}

第四步:开始调度更新 performConcurrentWorkOnRoot 函数。

    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );

第五步:在 performConcurrentWorkOnRoot 函数中,去通过 includesExpiredLane 函数 检查 root.expiredLanes 上是否有过期的 lane。如果有过期的 lane,也即一直被打断的低优先级赛道。那么会调用 renderRootSync 将低优先级直接提升至同步优先级进行渲染更新。低优先级任务得到了执行。

  const shouldTimeSlice =
    !includesBlockingLane(root, lanes) &&
    !includesExpiredLane(root, lanes) &&
    (disableSchedulerTimeoutInWorkLoop || !didTimeout);
  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);

自此解决饥饿问题