React中的调度器

213 阅读3分钟

自己的名词理解

为了达成某一个目的,协调多个任务的算法。这个目的比如拿浏览器来说,让用户感觉页面流畅。

React Schedule包的调度

SchedulerHostConfig

Schedule打断分两层含义

来个Demo

codesandbox.io/s/xenodochi…

高优先级打断低优先级(多个任务)

优先级队列的内容,此处省略

还是说说主要流程吧

  1. unstable_scheduleCallback调度当前最高优先级的工作 unstable_scheduleCallback

  2. 重新执行workLoop拿到优先级高的任务

时间切片用完了(单个任务yieldInterval

针对非同步优先级

当前work由于时间切片用尽,被打断(break)。没有其他更高优先级的work与它竞争,下一次还是当前work

yieldInterval分为 yield(可以理解暂停) interval(间隔)

中断(时间切片用完了)

workLoop

let currentTime = initialTime;
  advanceTimers(currentTime);
  currentTask = peek(taskQueue);
  while (
    currentTask !== null &&
    !(enableSchedulerDebugging && isSchedulerPaused)
  ) {
    if (
      currentTask.expirationTime > currentTime &&
      (!hasTimeRemaining || shouldYieldToHost())
    ) {
      //这里处理时间切片用完了的情况`当前work`由于时间切片用尽,被打断。没有其他更高优的work与他竞争,下一次perform还是当前work


      // This currentTask hasn't expired, and we've reached the deadline.
      break;
    }
    ...
  }

我们关注break的条件。

currentTask.expirationTime > currentTime && (!hasTimeRemaining || shouldYieldToHost())

翻译一下: 任务没过期,并且时间切片用完了

shouldYieldToHost

 shouldYieldToHost = function() {
      const currentTime = getCurrentTime();
      if (currentTime >= deadline) {
        // There's no time left. We may want to yield control of the main
        // thread, so the browser can perform high priority tasks. The main ones
        // are painting and user input. If there's a pending paint or a pending
        // input, then we should yield. But if there's neither, then we can
        // yield less often while remaining responsive. We'll eventually yield
        // regardless, since there could be a pending paint that wasn't
        // accompanied by a call to `requestPaint`, or other main thread tasks
        // like network events.
        if (needsPaint || scheduling.isInputPending()) {
          // There is either a pending paint or a pending input.
          return true;
        }
        // There's no pending input. Only yield if we've reached the max
        // yield interval.
        return currentTime >= maxYieldInterval;
      } else {
        // There's still time left in the frame.
        return false;
      }
    };
    ...

打开有道🤪

没时间了。我们可能希望放弃对主线程的控制,这样浏览器就可以执行高优先级的任务。主要的是绘制和用户输入。如果有一个挂起的油漆(paint)或一个挂起的输入,那么我们应该让步。

expirationTime计算公式: var expirationTime = startTime + timeout;

startTime

priorityLevel

startTime: 当前时间

timeout: 优先级对应的数字


// Times out immediately
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
// Eventually times out
var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
var LOW_PRIORITY_TIMEOUT = 10000;
// Never times out
var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;

// Tasks are stored on a min heap
var taskQueue = [];
var timerQueue = [];

由此可以得出,当任务是同步的优先级的时候 currentTask.expirationTime 永远小于currentTime所以永远不会break(也就是任务过期了,我要立马处理),react在commit阶段是同步的,因为采用的是同步优先级。

实践

同步优先级下,界面是一下子出来。而其他优先级是过度的出来。原因是时间切片用完了,把控制权交给浏览器,浏览器去paint

总结

时间切片是一种思想,核心是交出浏览器控制权(React将控制权交还给浏览器)。可以用许多方式实现如setTimeout yield等,React Schedule用了messageChannel。关键是 yieldInterval,比较鸡贼