react任务调度(一)

471 阅读3分钟

logo-og.png

任务池

我们知道,react把任务分成了不同优先级,即存在执行任务的先后顺序,所以react分成了2个任务池:

// 立即执行的任务
const taskQueue = []
// 延迟执行的任务
const timerQueue = []

taskQueue为当前正在执行的任务队列。timerQueue为延迟执行的任务队列。

单元任务

一个单独任务的描述:

type Task = {
  id: number, // 任务id, 全局递增
  callback: Function, // 任务要执行的函数
  priority: PriorityLevel, // 任务的优先级
  startTime: number, // 任务的开始时间
  expirationTime: number, // 任务的过期时间
  sortIndex: number, // 任务的排序
}

结合任务池和任务描述,当一个任务进来时,会获取当前时间(performance.now() / new Date())作为startTime.同时会传入一个delay参数。开始时间为:startTime = startTime + delay。如果delay没有值那就是立即执行的任务,当前任务会被推送到taskQueue中。如果有则会被认为是延迟任务,推送到timerQueue中。

调度函数:

type Task = {
  id: number, // 任务id, 全局递增
  callback: Function, // 任务要执行的函数
  priority: PriorityLevel, // 任务的优先级
  startTime: number, // 任务的开始时间
  expirationTime: number, // 任务的过期时间
  sortIndex: number, // 任务的排序
}
​
let taskIdCounter: number = 0function ScheduleCallback(
   priorityLevel: number, // 任务优先级
   callback: Function, // 执行函数
   options: { delay: number }
) {
     // 确定开始时间和过期时间
    let startTime: number
    if (option.delay) { // 这里可以对delay类型去做一些判断
      startTime = startTime + option.delay
    } else {
      startTime = performance.now() // 当前时间
    }
    
     const expirationTime = startTime + getTimeoutByPriorityLevel(priorityLevel)
     
     // 此刻可以定义一个任务了
     const task = {
       id: taskIdCounter++,
       callback,
       priority: priorityLevel,
       startTime,
       expirationTime,
       sortIndex: -1, // 后面再讲解
     }
     if (option.delay) {
       // 延迟任务
     } else {
       // 立即执行任务
     }
}

到这里了,我们来分析一下,立即执行任务要做些什么?

立即执行逻辑:

newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);
}

会把当前任务的sortIndex设置成任务的过期时间。当过期时间越近的时候,他的优先级越高。

这里稍微偏离主线说一下,比较任务池中,哪个优先级更高的函数

function compare(a: Node, b: Node) {
  // Compare sort index first, then task id.
  const diff = a.sortIndex - b.sortIndex;
  return diff !== 0 ? diff : a.id - b.id;
}

所以我们知道,当sortIndex越小的优先级越高,当sortIndex相同的则比较他们的任务id了,任务id其实就是任务进入任务池的先后顺序了。

设置完sortIndex以后会把当前的任务推荐立即执行的任务池当中,等待被调用。

然后判断是否当前是否有任务在调用,没有则执行requestHostCallback。我们看下requestHostCallback做了啥?

function requestHostCallback(callback) {
  scheduledHostCallback = callback
  if (!isMessageLoopRunning) {
    isMessageLoopRunning = true
    schedulePerformWorkUntilDeadline();
  }
}

其中的callback就是flushwork,flushwork就是真正调度任务,后面再详细说怎么去调度任务池中的任务。

schedulePerformWorkUntilDeadline

我们看下schedulePerformWorkUntilDeadline做了啥?

const channel = new MessageChannel();
​
const port = channel.port2;channel.port1.onmessage = performWorkUntilDeadline;
​
// 发送
schedulePerformWorkUntilDeadline = () => {
  port.postMessage(null);
};

全局首先会创建MessageChannel实例。然后会创建2个端口,会根据这2个端口来传数据。

发现schedulePerformWorkUntilDeadline其实就是发送了postMessage,其实就是调用了performWorkUntilDeadline:

const performWorkUntilDeadline = () => {
  if (scheduledHostCallback !== null) {
    const currentTime = getCurrentTime();
​
    // Keep track of the start time so we can measure how long the main thread
    // has been blocked.
    startTime = currentTime;
​
    const hasTimeRemaining = true;
    let hasMoreWork = true;
    try {
      hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
    } finally {
      if (hasMoreWork) {
        schedulePerformWorkUntilDeadline();
      } else {
        isMessageLoopRunning = false;
        scheduledHostCallback = null;
      }
    }
  } else {
    isMessageLoopRunning = false;
  }
};

该函数其实就是执行了scheduledHostCallback,scheduledHostCallback存储的是flushwork。

function flushWork(hasTimeRemaining: boolean, initialTime: number) {
  isHostCallbackScheduled = false;
​
  if (isHostTimeoutScheduled) {
    isHostTimeoutScheduled = false;
    //取消当前的任务
    cancelHostTimeout();
  }
​
  isPerformingWork = true;
​
  let previousPriorityLevel = currentPriorityLevel;
  try {
    return workLoop(hasTimeRemaining, initialTime);
  } finally {
    currentTask = null;
    currentPriorityLevel = previousPriorityLevel;
    isPerformingWork = false;
  }
}

Flushwork会判断当前是否有任务,如果有任务正在执行,会取消当前的任务。执行接下来新的高优先级的任务。 于是会调用了workLoop。 workLoop:在当前时间切片内循环执行任务。

至此我们知道 schedulePerformWorkUntilDeadline 其实就是利用宏任务去执行当前任务池中的任务。那么为什么不直接去执行flush work?如果利用宏任务才能到达目的,为什么不使用setTimeout/requestAnimationFrame呢?

:将在《react任务调度二》分析react的时间切片思想。