React Scheduler

scheduler.png

1. unstable_scheduleCallback

react调度过程的入口是unstable_scheduleCallback函数,该函数接收调度任务的优先级priorityLevel、任务函数callback以及option三个参数。priorityLevel用来计算任务的timeout时间,对应关系如下:

ImmediatePriority => IMMEDIATE_PRIORITY_TIMEOUT : -1
UserBlockingPriority => USER_BLOCKING_PRIORITY : 250
IdlePriority => IDLE_PRIORITY : 5000
LowPriority => LOW_PRIORITY_TIMEOUT : 10000
NormalPriority => NORMAL_PRIORITY_TIMEOUT : maxSigned31BitInt

option参数包含delay和timeout选项,分别决定任务的startTime和timout,这两者相加得到任务的过期时间(expirationTime)。unstable_scheduleCallback中新建的newTask结构如下:

var newTask = {  
 id: taskIdCounter++,  
 callback,  
 priorityLevel,  
 startTime,  
 expirationTime,  
 sortIndex: -1,  
 };

接下来将newTask按startTime的区别推入最小堆timeQueue或者taskQueue,开始调度过程(最小堆结构可以方便的从中取出优先级最高的任务)。

2. delayed task的处理

当task的startTime < currentTime时,说明这是一个延时任务,将该任务推入timeQueue,timeQueue是按startTime排列的最小堆结构。推入任务后,如果taskQueue为空且这个新推入的延时任务是timeQueue中的第一个任务,则到该任务的startTime时,调用handleTimeout(currentTime)将timeQueue中startTime >= currentTime的task推入taskQueue。

if (startTime > currentTime) {
    // This is a delayed task.
    newTask.sortIndex = startTime;
    push(timerQueue, newTask);
    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
      // All tasks are delayed, and this is the task with the earliest delay.
      if (isHostTimeoutScheduled) {
        // Cancel an existing timeout.
        cancelHostTimeout();
      } else {
        isHostTimeoutScheduled = true;
      }
      // Schedule a timeout.
      //startTime - currentTime秒后执行handleTimeout,将timeQueue中的task推入taskQueue
      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  }

3. taskQueue中task的调度过程

unstable_scheduleCallback如果新建的是一个非延时任务,则按照expirationTime排序推入最小堆taskQueue,接着调用requestHostCallback(flushWork)请求调度。

newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
// Schedule a host callback, if needed. If we're already performing work,
// wait until the next time we yield.
if (!isHostCallbackScheduled && !isPerformingWork) {
  isHostCallbackScheduled = true;
  requestHostCallback(flushWork);
}

flushWork中主要是改变一些状态变量,接下来进入workLoop。

isHostCallbackScheduled = false;
 if (isHostTimeoutScheduled) {
    // We scheduled a timeout but it's no longer needed. Cancel it.
    isHostTimeoutScheduled = false;
    cancelHostTimeout();
  }
  isPerformingWork = true;
  try{
   return workLoop(hasTimeRemaining, initialTime);
  }catch{
      currentTask = null;
    currentPriorityLevel = previousPriorityLevel;
    isPerformingWork = false;
  }

workLoop函数通过while循环执行taskQueue中的任务,当当前优先级最高的任务的expiraTime < currentTime并且当前时间片用完时,跳出workLoop循环,如果当前taskQueue中有更多的任务,则返回true等待下一次调度,否则返回false;

function workLoop(hasTimeRemaining, initialTime) {
  let currentTime = initialTime;
  advanceTimers(currentTime);
  currentTask = peek(taskQueue);
  while (
    currentTask !== null &&
    !(enableSchedulerDebugging && isSchedulerPaused)
  ) {
    // This currentTask hasn't expired, and we've reached the deadline.
    if (
      currentTask.expirationTime > currentTime &&
      (!hasTimeRemaining || shouldYieldToHost())
    ) {
      break;
    }
    const callback = currentTask.callback;
    if (callback !== null) {
      currentTask.callback = null;
      currentPriorityLevel = currentTask.priorityLevel;
      const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
      const continuationCallback = callback(didUserCallbackTimeout);
      currentTime = getCurrentTime();
      if (typeof continuationCallback === 'function') {
        currentTask.callback = continuationCallback;
      } else {
        if (currentTask === peek(taskQueue)) {
          pop(taskQueue);
        }
      }
      advanceTimers(currentTime);
    } else {
      pop(taskQueue);
    }
    currentTask = peek(taskQueue);
  }
  // Return whether there's additional work
  if (currentTask !== null) {
    return true;
  } else {
    let firstTimer = peek(timerQueue);
    if (firstTimer !== null) {
      requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
    }
    return false;
  }
}

4 requestHostCallback

flushWork相当于将taskQueue中的任务包裹起来供requestHostCallback执行,真正对taskQueue中任务的分片执行逻辑在requestHostCallback中。

requestHostCallback = function(callback) {  
     scheduledHostCallback = callback;  
     if (!isMessageLoopRunning) {  
         isMessageLoopRunning = true;  
         port.postMessage(null);  
     }  
 };

flushWork传人后保存在全局变量scheduledHostCallback中,postMessage方法会创建一个宏任务,且给这个宏任务分配的默认执行时间是yieldInterval=5ms, 这样每执行5ms就有机会将控制权交还给浏览器,从而不阻塞浏览器的UI渲染task,以及一些高优先级任务的入堆,使得即使在帧率较高的浏览器上也能保持较好的反应速度。
宏任务的执行在postMessage的onMessage回调中:

channel.port1.onmessage = performWorkUntilDeadline;
const performWorkUntilDeadline = () => {
    if (scheduledHostCallback !== null) {
      const currentTime = getCurrentTime();
      // Yield after `yieldInterval` ms, regardless of where we are in the vsync
      // cycle. This means there's always time remaining at the beginning of
      // the message event.
      deadline = currentTime + yieldInterval;
      const hasTimeRemaining = true;
      try {
        const hasMoreWork = scheduledHostCallback(
          hasTimeRemaining,
          currentTime,
        );
        if (!hasMoreWork) {
          isMessageLoopRunning = false;
          scheduledHostCallback = null;
        } else {
          // If there's more work, schedule the next message event at the end of the preceding one.
          port.postMessage(null);
        }
      } catch (error) {
        // If a scheduler task throws, exit the current browser task so the
        // error can be observed.
        port.postMessage(null);
        throw error;
      }
    } else {
      isMessageLoopRunning = false;
    }
    // Yielding to the browser will give it a chance to paint, so we can
    // reset this.
    needsPaint = false;
  };

karl
78 声望5 粉丝