【React原理 - 任务调度之中断恢复】

2024-08-28 20:44

本文主要是介绍【React原理 - 任务调度之中断恢复】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

概览

本文紧接上文介绍React调度的时间分片中任务中断和恢复,由于篇幅过长,所以拆成了两篇。上文主要介绍了调度器中的优先级和调度任务的触发、注册和调度循环。本文主要从任务调度入手介绍调度任务之后发送了什么,即在协调器中如何进行到fiber构造的一系列流程。所以推荐在阅读本文之前先阅读上文,对调度有个基本认识。

前情回顾

先看下图了解React中宏观上整体执行流程图:

在这里插入图片描述
React有两大循环:Scheduler workLoop(调度循环)、fiber workLoop(fiber构造循环)。在上文中介绍了调度循环,其workLoop代码如下:

// packages/scheduler/src/forks/Scheduler.js
function workLoop(hasTimeRemaining, initialTime) {let currentTime = initialTime;advanceTimers(currentTime);currentTask = peek(taskQueue);while (currentTask !== null &&!(enableSchedulerDebugging && isSchedulerPaused)) {if (currentTask.expirationTime > currentTime &&(!hasTimeRemaining || shouldYieldToHost())) {// This currentTask hasn't expired, and we've reached the deadline.break;}const callback = currentTask.callback;if (typeof callback === 'function') {currentTask.callback = null;currentPriorityLevel = currentTask.priorityLevel;const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;const continuationCallback = callback(didUserCallbackTimeout);currentTime = getCurrentTime();if (typeof continuationCallback === 'function') {currentTask.callback = continuationCallback;} else {if (currentTask === peek(taskQueue)) {pop(taskQueue);}}advanceTimers(currentTime);} else {pop(taskQueue);}currentTask = peek(taskQueue);}// Return whether there's additional workif (currentTask !== null) {return true;} else {const firstTimer = peek(timerQueue);if (firstTimer !== null) {requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);}return false;}
}

该函数具体逻辑在上文已经介绍,所以本文不再细说,主要看while循环中执行callback回调,即const continuationCallback = callback(didUserCallbackTimeout);,这里的callback就是在Reconciler通过scheduleCallback传入的performConcurrentWorkOnRoot函数,所以每次调度执行的就是该函数,来进行fiber的构造。

performConcurrentWorkOnRoot主要逻辑如下:

// root: 根fiber节点
// didTimeout:当前调度任务是否过期: currentTask.expirationTime <= currentTime
function performConcurrentWorkOnRoot(root, didTimeout) {// 当前任务没有过期并且不是阻塞任务,而且任务没有过期的时候开启分片,否则就进行同步更新,也避免了频繁被高优先级中断而导致低优先级任务无法执行的问题const shouldTimeSlice =!includesBlockingLane(root, lanes) &&!includesExpiredLane(root, lanes) &&(disableSchedulerTimeoutInWorkLoop || !didTimeout);let exitStatus = shouldTimeSlice? renderRootConcurrent(root, lanes): renderRootSync(root, lanes);if (exitStatus !== RootInProgress) {// The render completed.const finishedWork: Fiber = (root.current.alternate: any);root.finishedWork = finishedWork;root.finishedLanes = lanes;finishConcurrentRender(root, exitStatus, lanes); // commitRoot}ensureRootIsScheduled(root, now()); // 调度ensureRootIsScheduled开启下一次调度,如果有中断的任务,则再次等待调度if (root.callbackNode === originalCallbackNode) {return performConcurrentWorkOnRoot.bind(null, root); // 保存当前的上下文,如果任务被中断就根据该上下文进行恢复,即调度循环中的continuationCallback}return null;
}

从代码能看出来并不是所有的并发任务都能分片的,只有当前任务没有过期、不是阻塞性任务(比如用户交互事件:onclick、input…)、以及没有超时的任务,才会开启分片执行renderRootConcurrent函数进行并发渲染。

由于disableSchedulerTimeoutInWorkLoop表示是否禁用超时检查,并且该值默认是false。所以每次执行前都会检查didTimeout当前任务是否超时,如果超时则进行同步处理不可中断。以此来避免低优先级任务一直被高优先级任务中断而导致始终无法执行的问题。

如上可知,根据是否开启分片来判断是同步(renderRootSync)还是异步执行(renderRootConcurrent)。本文主要介绍并发中断和恢复,所以主要介绍异步执行即renderRootConcurrent函数的逻辑。

renderRootConcurrent函数如下:

function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {const prevExecutionContext = executionContext;executionContext |= RenderContext;if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {prepareFreshStack(root, lanes); // 全局状态变量初始化 包括workInProgressRootRenderLanes等}do {try {workLoopConcurrent();break;} catch (thrownValue) {handleError(root, thrownValue);}} while (true);executionContext = prevExecutionContext;// Check if the tree has completed.if (workInProgress !== null) {return RootInProgress;} else {// Set this to null to indicate there's no in-progress render.workInProgressRoot = null;workInProgressRootRenderLanes = NoLanes;// Return the final exit status.return workInProgressRootExitStatus;}}

在代码中提到了栈帧即prepareFreshStack当更新的root或者优先级变化时,则认为是一次全新的调度,所以会调用prepareFreshStack初始化一个栈帧。该栈帧主要在内存中用全局变量保存当前fiber构造的状态,以便被高优先级任务中断之后能通过该组全局变量来恢复中断是的状态。因为在被高优先级任务中断时会将当前状态保存在全局变量中,包括上面的workInProgressRootRenderLanes,然后在高优先级任务执行之后会通过该优先级判断,根据全局变量获取中断时的内存状态,并恢复执行。并且workInProgress 指针指向这些全局变量, 即 workInProgress = HostRootFiber.alternate

初始化栈帧之后会死循环调用workLoopConcurrent来进行fiber构造:

function workLoopConcurrent() {// Perform work until Scheduler asks us to yieldwhile (workInProgress !== null && !shouldYield()) {performUnitOfWork(workInProgress);}}

通过while一直调用performUnitOfWork来进行beginWork、completeWork构造fiber节点。具体fiber构造流程可查看这篇文章:【React架构 - Fiber构造循环】

workLoopConcurrent执行完成获取被中断而跳出死循环后,会根据workInProgress来判断当然任务是否完成进而返回RootInProgress / workInProgressRootExitStatus。然后在performConcurrentWorkOnRoot
函数中exitStatus来接收该返回值。当执行完成之后会设置完成状态finishedWork、finishedLanes并通过finishConcurrentRender来触发commit将创建的新fiber树提交到commit阶段进行真实dom的操作。

然后会调用ensureRootIsScheduled来再发出一次调度(MessageChannel宏任务),在下一次事件循环时执行,并保存当前的上下文,如果任务被中断就根据该上下文进行恢复,即调度循环中的continuationCallback

callback即performConcurrentWorkOnRoot执行完之后会通过continuationCallback接收返回值,即上面提到的performConcurrentWorkOnRoot(未执行完时会返回performConcurrentWorkOnRoot函数并传入保存有中断时的状态变量的root)/ null(执行完成)。然后会判断该返回值,如果未完成则会将performConcurrentWorkOnRoot绑定到当前任务的callback,该任务仍然在taskQueue中,下一次任务调度时继续从taskQueue中取出执行,执行完成之后则将调度任务从taskQueue中移出。

const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();if (typeof continuationCallback === 'function') {currentTask.callback = continuationCallback;} else {if (currentTask === peek(taskQueue)) {pop(taskQueue);}}advanceTimers(currentTime);

总结

在React中主要是通过shouldYieldToHost来进行时间分片(5ms)或者高优先级任务中断,然后当前任务中断时会将此时的状态保存在全局变量并绑定在workInProgress中(栈帧),并返回performConcurrentWorkOnRoot(传入保存了此时状态的root即workInProgress)作为调度任务的callback,继续保存taskQueue中。然后调用ensureRootIsScheduled再发起一起调度任务,待高优先级任务执行完成之后,新一次调度继续从taskQueue中取出该任务,并利用workInProgress保持的状态恢复中断前进度,并继续执行。

  • 使用shouldYieldToHost时间分片、高优先级中断
  • 利用workInProgress保存状态恢复
  • 未完成时更新调度任务的callback继续保留在taskQueue中,下一次调度获取

具体详细流程可以查看该图:(图片来源来自网上)
在这里插入图片描述

这篇关于【React原理 - 任务调度之中断恢复】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1115840

相关文章

全面解析HTML5中Checkbox标签

《全面解析HTML5中Checkbox标签》Checkbox是HTML5中非常重要的表单元素之一,通过合理使用其属性和样式自定义方法,可以为用户提供丰富多样的交互体验,这篇文章给大家介绍HTML5中C... 在html5中,Checkbox(复选框)是一种常用的表单元素,允许用户在一组选项中选择多个项目。本

HTML5 搜索框Search Box详解

《HTML5搜索框SearchBox详解》HTML5的搜索框是一个强大的工具,能够有效提升用户体验,通过结合自动补全功能和适当的样式,可以创建出既美观又实用的搜索界面,这篇文章给大家介绍HTML5... html5 搜索框(Search Box)详解搜索框是一个用于输入查询内容的控件,通常用于网站或应用程

Python中使用uv创建环境及原理举例详解

《Python中使用uv创建环境及原理举例详解》uv是Astral团队开发的高性能Python工具,整合包管理、虚拟环境、Python版本控制等功能,:本文主要介绍Python中使用uv创建环境及... 目录一、uv工具简介核心特点:二、安装uv1. 通过pip安装2. 通过脚本安装验证安装:配置镜像源(可

CSS3中的字体及相关属性详解

《CSS3中的字体及相关属性详解》:本文主要介绍了CSS3中的字体及相关属性,详细内容请阅读本文,希望能对你有所帮助... 字体网页字体的三个来源:用户机器上安装的字体,放心使用。保存在第三方网站上的字体,例如Typekit和Google,可以link标签链接到你的页面上。保存在你自己Web服务器上的字

html 滚动条滚动过快会留下边框线的解决方案

《html滚动条滚动过快会留下边框线的解决方案》:本文主要介绍了html滚动条滚动过快会留下边框线的解决方案,解决方法很简单,详细内容请阅读本文,希望能对你有所帮助... 滚动条滚动过快时,会留下边框线但其实大部分时候是这样的,没有多出边框线的滚动条滚动过快时留下边框线的问题通常与滚动条样式和滚动行

Mysql的主从同步/复制的原理分析

《Mysql的主从同步/复制的原理分析》:本文主要介绍Mysql的主从同步/复制的原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录为什么要主从同步?mysql主从同步架构有哪些?Mysql主从复制的原理/整体流程级联复制架构为什么好?Mysql主从复制注意

Nacos注册中心和配置中心的底层原理全面解读

《Nacos注册中心和配置中心的底层原理全面解读》:本文主要介绍Nacos注册中心和配置中心的底层原理的全面解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录临时实例和永久实例为什么 Nacos 要将服务实例分为临时实例和永久实例?1.x 版本和2.x版本的区别

使用vscode搭建pywebview集成vue项目实践

《使用vscode搭建pywebview集成vue项目实践》:本文主要介绍使用vscode搭建pywebview集成vue项目实践,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录环境准备项目源码下载项目说明调试与生成可执行文件核心代码说明总结本节我们使用pythonpywebv

apache的commons-pool2原理与使用实践记录

《apache的commons-pool2原理与使用实践记录》ApacheCommonsPool2是一个高效的对象池化框架,通过复用昂贵资源(如数据库连接、线程、网络连接)优化系统性能,这篇文章主... 目录一、核心原理与组件二、使用步骤详解(以数据库连接池为例)三、高级配置与优化四、典型应用场景五、注意事

使用Python和Tkinter实现html标签去除工具

《使用Python和Tkinter实现html标签去除工具》本文介绍用Python和Tkinter开发的HTML标签去除工具,支持去除HTML标签、转义实体并输出纯文本,提供图形界面操作及复制功能,需... 目录html 标签去除工具功能介绍创作过程1. 技术选型2. 核心实现逻辑3. 用户体验增强如何运行