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