React: Schedule, our small goal this year is 100 million

Hello, this is Xiao Chen. Today I will tell you a story

Tell a story:

Once upon a time, there was a Z company. Its CEO was named React. It had a little brother or leader, schedule

Schedule is responsible for digesting the big cake drawn by boss React every day, and then breaking it down into small tasks for his younger brothers to finish. He is also responsible for sorting priorities and scheduling tasks of his younger brothers.

Then how does the schedule prioritize these tasks? It thinks of the simplest way: assign priority to these tasks by deadline or expiration time. The shorter the expiration time, the more urgent the task is, and assign coolies (the younger brother below) to finish it. You can work slowly later, there is also a type of task has passed its deadline, the expired task is the highest priority, there is no way, after the extension will also be completed, poor programmer little brother.

The scheduler and the leader break up the boss’s pie and then prioritize the tasks according to the deadline, so the programmer starts taking the tasks

The programmer brother A accepts Task1 and Task2, so he assigns himself A task list to do task1 first, then Task2, so he enters the Render phase and is working on Task1

The boss assigned a very urgent demand to scheduler according to the business needs, which caused the programmer a lot of trouble. The scheduler said, “Oh, I can’t do anything, I need to work overtime, and put this urgent demand in a queue. The programmer can only do one task at a time, so HE put in a queue. Work extra hours on your most urgent needs.

What follows is intense overtime… (This stage is called the Render stage)

Finally, the programmer worked overtime to complete all the tasks and handed them over to the test verification (COMMIT stage).

Task2 has not yet reached the deadline, so please put it on hold. It is a difficult task, so do it in your spare time. Finish task0 and Task1 first and have time to work on Task2

To get down to business:

When we are in a similar search will find the following search box components, components are divided into search and display list of search results, we expect input box to immediately response, search list can have waiting time, if the search list data volume is very big, at the time of rendering is done, we input some words again, because of the high priority is of user input events, So stop rendering the result list, which leads to prioritization and scheduling between tasks

Scheduler

We know that if the application takes a long JS execution time, such as one frame longer than the device, the device will not draw smoothly.

The main functions of Scheduler are time slice and scheduling priority. React takes up a certain js execution time when comparing node differences. The Scheduler internally uses MessageChannel to specify a time slice before the browser draws. If React does not complete the comparison within the specified time, Scheduler forces the browser to relinquish execution

Time slice

The js execution time in a browser frame is as follows

RequestIdleCallback allows you to perform unfinished tasks if you have free time after the redrawing of the browser. Therefore, in order not to affect the redrawing of the browser, you can perform performance costly calculations in the requestIdleCallback. However, due to the problems of compatibility and unstable trigger timing of requestIdleCallback, MessageChannel is used in Scheduler to implement requestIdleCallback. Use setTimeout if the current environment does not support MessageChannel.

The render phase and commit phase are executed after performUnitOfWork. If the CPU calculation is not complete within a browser frame, js execution is given to the browser. ShouldYield is to tell if the remaining time is used up. In the source code each time slice is 5ms, this value is adjusted according to the FPS of the device.

function workLoopConcurrent() {
  while(workInProgress ! = =null && !shouldYield()) {// If the fiber chain is not finished, it is not paused or broken
    performUnitOfWork(workInProgress);// Execute the render phase}}Copy the code
function forceFrameRate(fps) {// Calculate the time slice
  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;// The time slice defaults to 5ms}}Copy the code

Suspension of tasks

You have a section in the shouldYield function, so you know that if the current time is greater than the time the task started + the yieldInterval, you are interrupting the task.

// Deadline = currentTime + yieldInterval The deadline is calculated in performWorkUntilDeadline
if (currentTime >= deadline) {
  / /...
	return true
}
Copy the code

Scheduling priority

There are two functions in Scheduler that create tasks with priority

  • RunWithPriority: as a priority to perform the callback, if the task is synchronous, priority is ImmediateSchedulerPriority

    function unstable_runWithPriority(priorityLevel, eventHandler) {
      switch (priorityLevel) {//5 priorities
        case ImmediatePriority:
        case UserBlockingPriority:
        case NormalPriority:
        case LowPriority:
        case IdlePriority:
          break;
        default:
          priorityLevel = NormalPriority;
      }
    
      var previousPriorityLevel = currentPriorityLevel;// Save the current priority
      currentPriorityLevel = priorityLevel;// Assign priorityLevel to currentPriorityLevel
    
      try {
        return eventHandler();// The callback function
      } finally {
        currentPriorityLevel = previousPriorityLevel;// Restore the previous priority}}Copy the code
  • ScheduleCallback: Callback is registered with a priority and executed at the appropriate time because scheduleCallback is more granular than runWithPriority because it involves calculations of expiration time.

    • The higher the priority, the smaller the priorityLevel, the closer the expirationTime is to the current time. Var expirationTime = startTime + timeout; For example, IMMEDIATE_PRIORITY_TIMEOUT=-1, then var expirationTime = startTime + (-1); Is less than the current time, so execute immediately.

    • The small top heap is used in the process of scheduleCallback scheduling, so we can find the task with the highest priority in the complexity of O(1). If you don’t know, you can refer to the information or refer to my Leetcode algorithm introduction series. The small top heap stores tasks in the source code. Each time peek retrieves the task closest to the expiration date.

    • In scheduleCallback, unexpired tasks are stored in timerQueue and expired tasks are stored in taskQueue.

      After a newTask is created, check whether the newTask has expired and add it to the timerQueue. If no task in the taskQueue has expired and the task in the timerQueue whose time is closest to expiration is newTask, set a timer. Join the taskQueue when it expires.

      When there are tasks in timerQueue, the earliest expired tasks are fetched and executed.

function unstable_scheduleCallback(priorityLevel, callback, options) {
  var currentTime = getCurrentTime();

  var startTime;// Start time
  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:// The higher the priority, the smaller the 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;// The higher the priority, the smaller the expiration time

  var newTask = {/ / the new task
    id: taskIdCounter++,
    callback// The callback function
    priorityLevel,
    startTime,// Start time
    expirationTime,// Expiration time
    sortIndex: -1};if (enableProfiling) {
    newTask.isQueued = false;
  }

  if (startTime > currentTime) {// There is no expiration date
    newTask.sortIndex = startTime;
    push(timerQueue, newTask);/ / to join timerQueue
    // There are no expired tasks in taskQueue, and the task whose expiry time is closest to timerQueue is newTask
    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
      if (isHostTimeoutScheduled) {
        cancelHostTimeout();
      } else {
        isHostTimeoutScheduled = true;
      }
      // The taskQueue is added to the timer when it expiresrequestHostTimeout(handleTimeout, startTime - currentTime); }}else {
    newTask.sortIndex = expirationTime;
    push(taskQueue, newTask);/ / to join taskQueue
    if (enableProfiling) {
      markTaskStart(newTask, currentTime);
      newTask.isQueued = true;
    }
    if(! isHostCallbackScheduled && ! isPerformingWork) { isHostCallbackScheduled =true;
      requestHostCallback(flushWork);// Execute expired tasks}}return newTask;
}
Copy the code

Video explanation (efficient learning) :Click on the learning

React source code

1. Introduction and questions

2. React design philosophy

React source code architecture

4. Source directory structure and debugging

5. JSX & core API

Legacy and Concurrent mode entry functions

7. Fiber architecture

8. Render phase

9. The diff algorithm

10. com MIT stage

11. Life cycle

12. Status update process

13. Hooks the source code

14. Handwritten hooks

15.scheduler&Lane

16. Concurrent mode

17.context

18 Event System

19. Write the React mini

20. Summary & Answers to interview questions in Chapter 1