Boss react said: schedule, our small goal this year is 100 million
hello, this is Xiaochen. Let's tell a story today
Tell a story:
Once upon a time, there was a z company. The ceo of z company was react. He accepted a little brother or a little leader, schedule
schedule is responsible for digesting the big cakes drawn by the boss react every day, and then disassembling them into small tasks for the younger brothers to complete. It is also responsible for prioritizing and scheduling the tasks of the younger brothers.
How does the schedule prioritize these tasks? It thinks of the simplest way. It uses deadline or expiration time to prioritize these tasks. The shorter the expiration time, the more urgent the task is. Quickly assign coolies (the younger brother below) to complete it. The longer the expiration time, the less urgent the task is. You can work slowly in the future, Another type of task has passed its deadline. The overdue task has the highest priority. There is no way. It needs to be completed after the delay. Poor little brother of the programmer.
image-20211129163824936
So the little leader and scheduler broke the boss's cake, and then prioritized these small tasks according to the deadline, so the little brother of the programmer began to take over the task
The programmer's younger brother A accepted task1 and task2, so he made A task list for himself. He did task1 first and then task2 according to the priority, so he entered the intensive development (render stage) and was doing task1
image-20211129163900829
However, due to the unexpected situation, the boss issued a very urgent demand to the scheduler according to the needs of the business, which hurt the programmer's brother. The scheduler said, alas, there's no way. Work overtime and plug this very urgent demand into a team. The programmer's brother is single threaded and can only do one task at a time, so plug in a team and work overtime to do the most urgent demand task0.
image-20211129163918924
Then there is the intensive overtime... (this stage is called render stage)
Finally, with disdainful efforts, the programmer finally worked overtime to complete all tasks and handed them to the test and verification (commit stage),
image-20211129164136855
The above situation is interrupted when there are urgent tasks. Another situation is that the big cake given by the boss is difficult to digest, but this task2 has not reached the deadline. The little brother of the programmer has encountered difficulties in doing this task, so let it go first. Anyway, it is a difficult task. Do it in your spare time. First complete task0 and task1 with high priority and have time to do task2
image-20211129171041192
Get to the point:
When we search in the search box component like the following, we will find that the component is divided into search part and search result display list. We expect the input box to respond immediately, and the search element list can have waiting time. If the search list has a large amount of data, we enter some text during rendering, because the priority of user input events is very high, Therefore, it is necessary to stop the rendering of the result list, which leads to the priority and scheduling between different tasks
react source code 15.5
Scheduler
We know that if the application takes a long js execution time, such as more than one frame of the device, the drawing of the device will be not smooth.
The Scheduler's main functions are time slicing and scheduling priority. React will occupy a certain js execution time when comparing node differences. The Scheduler uses MessageChannel to specify a time slice before the browser draws. If react fails to complete the comparison of differences within the specified time, the Scheduler will forcibly hand over the execution right to the browser
react source code 15.3
Time slice
The execution time of js in one frame of the browser is as follows
react source code 15.1
requestIdleCallback can perform unfinished tasks if it is still free after the browser redraws and rearranges. Therefore, in order not to affect the redrawing and rearrangement, the browser can perform performance consumption calculation in requestIdleCallback. However, due to the problems of compatibility and unstable trigger time in requestIdleCallback, The scheduler uses MessageChannel to implement requestIdleCallback. If the current environment does not support MessageChannel, it uses setTimeout.
The render phase and the commit phase will be executed after performingunitofwork (the starting point of the render phase). If the calculation of the cup has not been completed in a frame of the browser, the js execution right will be given to the browser. In the workLoopConcurrent function, shouldYield is used to judge whether the remaining time has been exhausted. In the source code, each time slice is 5ms, and this value will be adjusted according to the fps of the device.
function workLoopConcurrent() { while (workInProgress !== null && !shouldYield()) {//If the fiber chain has not been traversed, it has not been suspended or interrupted performUnitOfWork(workInProgress);//Execute render phase } }
function forceFrameRate(fps) {//Calculate 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 default time slice is 5ms } }
Suspension of tasks
There is a period in the shouldYield function, so you can know that if the current time is greater than the task start time + yield interval, the task will be interrupted.
//deadline = currentTime + yieldInterval. deadline is calculated in performWorkUntilDeadline function if (currentTime >= deadline) { //... return true }
Scheduling priority
There are two functions in the Scheduler to create tasks with priority
- runWithPriority: execute callback with a priority. If it is a synchronous task, the 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(); / / callback function} finally {currentprioritylevel = previousprioritylevel; / / restore the previous priority}}
- scheduleCallback: register the callback with a priority and execute it at an appropriate time. Because it involves the calculation of expiration time, the granularity of scheduleCallback is finer than that of runWithPriority.
- In scheduleCallback, priority means expiration time. The higher the priority, the smaller the priorityLevel, and the closer the expiration time is to the current time. var expirationTime = startTime + timeout; For example, IMMEDIATE_PRIORITY_TIMEOUT=-1, var expirationTime = startTime + (-1); It is less than the current time, so it should be executed immediately.
- The small top heap is used in the scheduling process of scheduleCallback, so we can find the task with the highest priority in the complexity of O(1). If we don't know, we can consult the data or mine leetcode algorithm lecture series , tasks are stored in the top heap of the source code. Each peek can get the task closest to the expiration time.
- In scheduleCallback, unexpired tasks are stored in timerQueue, and expired tasks are stored in taskQueue. After a new newTask task is created, judge whether the newTask has expired. If it has not expired, join the timerQueue. If there is no expired task in the taskQueue at this time, and the task closest to the expiration time in the timerQueue is just a newTask, set a timer and join the taskQueue at the expiration time. When there are tasks in the timerQueue, the earliest expired tasks are retrieved for execution.
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 = {//New task id: taskIdCounter++, callback//Callback function priorityLevel, startTime,//start time expirationTime,//Expiration time sortIndex: -1, }; if (enableProfiling) { newTask.isQueued = false; } if (startTime > currentTime) {//Not expired newTask.sortIndex = startTime; push(timerQueue, newTask);//Join timerQueue //There are no expired tasks in the taskQueue. The task closest to the expiration time in the timerQueue happens to be a newTask if (peek(taskQueue) === null && newTask === peek(timerQueue)) { if (isHostTimeoutScheduled) { cancelHostTimeout(); } else { isHostTimeoutScheduled = true; } //Timer. When the expiration time is reached, it will be added to taskQueue requestHostTimeout(handleTimeout, startTime - currentTime); } } else { newTask.sortIndex = expirationTime; push(taskQueue, newTask);//Join taskQueue if (enableProfiling) { markTaskStart(newTask, currentTime); newTask.isQueued = true; } if (!isHostCallbackScheduled && !isPerformingWork) { isHostCallbackScheduled = true; requestHostCallback(flushWork);//Perform overdue tasks } } return newTask; }
react source code 15.2