In the previous

React Scheduler

Last time we talked about the priority of tasks and how to add tasks to the list based on priority (expiration time). Today we will analyze how to execute tasks at the right time.

1 requestIdleCallback pollyfill

The requetAnimationFrame has the disadvantage of emulating requestIdleCallback with requetAnimationFrame. If the TAB is currently inactive, RequestAnimationFrame is not working, so a combination of requestAnimationFrame and setTimeout is required to ensure that the task executes. This is the end of the above said requestAnimationFrameWithTimeout effect, the current TAB is active, the equivalent of requestAnimationFrame in scheduling tasks, cut the current TAB to the inactive setTimeout to take over the task execution. In order to understand and convenient, below we’ll use requestAnimationFrame requestAnimationFrameWithTimeout.

Process of 0.

Let’s start by describing the entire execution process, recording the start time of each frame in the rAF callback at the start of each frame, calculating the expiration time of each frame, and then sending the message via messageChannel. It receives the message in the callback of the messageChannel at the end of the frame and compares the expiration time of the current frame with the current time to determine whether the current frame can execute the task. If it can, the first task will be taken out of the task chain and executed successively. After executing as many tasks as possible, if there are still tasks, the next frame will be rescheduled.

1. Declare variables

var scheduledHostCallback = null; Var timeoutTime = -1; // Expiration time of firstCallbackNode task var activeFrameTime = 33; Var frameDeadline = 0; var frameDeadline = 0; // Represents the expiration time of a frame, calculated using the rAF callback parameter t plus activeFrameTimeCopy the code

2. Calculate the cutoff time for each frame

First we use requestAnimationFrame to calculate the expiration time for each frame

// rAF's callback is at the beginning of each frame, so it is suitable for light tasks that would otherwise block rendering.functionAnimationTick (rafTime) {// Recurse when there are tasks, no work is required if there are no tasksif(scheduledHostCallback ! == null) {requestAnimationFrame(animationTick)} FrameDeadline = rafTime + activeFrameTime; } // Somewhere will call requestAnimationFrame(animationTick)Copy the code

There is an optimization process for each frame rendering time in the source code, and the rendering time of each frame will be continuously compressed in the rendering process to reach the refresh frequency of the system (16.6ms at 60Hz). I’m going to skip this because it’s not important, but let’s say it’s 33ms.

3. Create a message channel

var channel = new MessageChannel(); var port = channel.port2; //port2 is used to send messages channel.port1.onMessage =function(event) {// the onMessage callback function is called after a paint frame is finished, so it is suitable for heavy tasks and keeps the page smooth and smooth.Copy the code

4. Perform tasks

The next step is to send a message to the channel in the animationTick, and then decide in the port1 callback whether or not the current frame should perform a task, how many tasks to perform, etc.

     functionAnimationTick (rafTime) {// Recurse when there are tasks, no work is required if there are no tasksif(scheduledHostCallback ! == null) {requestAnimationFrame(animationTick)} FrameDeadline = rafTime + activeFrameTime; // New code to do something at the end of the current frame port.postmessage (undefined); // If the current frame is not expired, the current frame is not available. // If the current frame is expired, the current frame is not available. Again, check if the current task firstCallbackNode is expired, and if it is, execute the task as well; If the current task has not expired, it is not in a hurry, so it is not executed until the next frame. channel.port1.onmessage =function(event) { var currentTime = getCurrentTime(); // Get the current time, var didTimeout =false; // Whether to expireif(frameDeadline - currentTime <= 0) {// The current frame expiresif(timeoutTime <= currentTime) {// Current task expiration // timeoutTime is the current task expiration time, which will be assigned somewhere. didTimeout =true;
            } else{// The current frame is out of date due to browser rendering, etc., so proceed to the next framereturn; }} // The current frame is not out of date; 2 is the current frame expires and the current task expires, which is the second one aboveifThe logic of. ScheduledHostCallback (didTimeout)}Copy the code

5. Actuators

Above the actuator scheduledHostCallback flushWork below, flushWork according to didTimeout parameters have two kinds of processing logic, if true, would put the task list all the overdue tasks to perform again; If false, perform as many tasks as possible before the current frame expires.

    function flushWork(didTimeout) {
        if(didTimeout) {// Task expiredwhile(firstCallbackNode ! == null) { var currentTime = getCurrentTime(); // Get the current timeif(firstCallbackNode expirationTime < = currentTime) {/ / if the team's first task time is smaller than the current time, that is out of datedo{ flushFirstCallback(); // Execute the first task, remove the first task from the list, and set the second task as the first task. Executing a task may create a new task, and then insert the new task into the task list.while( firstCallbackNode ! == null && firstCallbackNode.expirationTime <= currentTime );continue;
                }
                break; }}else}}Copy the code

Notice that there are two while loops above. The outer while loop gets the current time each time, and the inner loop uses this current time to determine if the task is expired and executes. So when the inner layer performs several tasks, the current time will move forward one more block. The outer loop then retrieves the current time until no tasks expire or there are no tasks left.

Let’s look at the processing without expiration

    function flushWork(didTimeout) {
        if(didTimeout) {// Task expired... }else{// The current frame has spare time,whileThe logic is to execute the task as long as there is a task and the current frame is not expired.if(firstCallbackNode ! == null) {do{ flushFirstCallback(); // Execute the first task, remove the first task from the list, and set the second task as the first task. Executing a task may create a new task, and then insert the new task into the task list.while (firstCallbackNode !== null && !shouldYieldToHost());
             }
        }
    }
Copy the code

ShouldYieldToHost means the current frame is out of date, otherwise it’s not out of date. Each time the while performs this judgment.

    shouldYieldToHost = function() {// If the cutoff time of the current frame is smaller than the current timetrue, indicating that the current frame is expiredreturn frameDeadline <= getCurrentTime();
    };
Copy the code

Let’s continue with flushWork

     function flushWork(didTimeout) {
        if(didTimeout) {// Task expired... }else{// The current frame has spare time... } // Finally, if there are any tasks, start a new task execution scheduleif(firstCallbackNode ! == null) { ensureHostCallbackIsScheduled(); } // Finally, if there are still tasks and the tasks have the highest priority, execute them all. flushImmediateWork(); }Copy the code

This article is relatively brief, there are a large number of flags in the source code, which are used to prevent reentry, defense judgment, etc., and consider the logic of the scenario where new tasks are constantly added in the process of task execution. This one is for interested readers to figure out for themselves.

2 summary

Finally, the overall task scheduling process is described

  • 1. The task determines the expiration time based on priority and the current time when it joins
  • 2. Tasks are added to the task list according to their expiration time
  • Task scheduling can be initiated in two situations: 1) when the task list is created from scratch; 2) when a new task with the highest priority is added to the task list.
  • 4. Task scheduling refers to executing tasks at the right time, which is passed hererequestAnimationFrameandmessageChannelTo simulate the
  • 5,requestAnimationFrameThe callback is executed at the beginning of the frame to calculate the expiration time of the current frame and to enable recursion,messageChannelThe callback is executed at the end of the frame to determine whether the task will be executed in the current frame (or next frame) based on the expiration time of the current frame, the current time, and the expiration time of the first task in the task list.
  • 6. If a task is executed, determine how to execute it based on whether the task is expired. If the task expires, all the expired tasks in the task list will be executed until there are no expired tasks or no tasks. If the task is not expired, it will execute as many tasks as possible before the current frame expires. Finally, if there are any tasks left, go back to Step 5, move to the next frame and start the process again.