【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

相关文章

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

深度解析Spring AOP @Aspect 原理、实战与最佳实践教程

《深度解析SpringAOP@Aspect原理、实战与最佳实践教程》文章系统讲解了SpringAOP核心概念、实现方式及原理,涵盖横切关注点分离、代理机制(JDK/CGLIB)、切入点类型、性能... 目录1. @ASPect 核心概念1.1 AOP 编程范式1.2 @Aspect 关键特性2. 完整代码实

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重

前端如何通过nginx访问本地端口

《前端如何通过nginx访问本地端口》:本文主要介绍前端如何通过nginx访问本地端口的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、nginx安装1、下载(1)下载地址(2)系统选择(3)版本选择2、安装部署(1)解压(2)配置文件修改(3)启动(4)

HTML中meta标签的常见使用案例(示例详解)

《HTML中meta标签的常见使用案例(示例详解)》HTMLmeta标签用于提供文档元数据,涵盖字符编码、SEO优化、社交媒体集成、移动设备适配、浏览器控制及安全隐私设置,优化页面显示与搜索引擎索引... 目录html中meta标签的常见使用案例一、基础功能二、搜索引擎优化(seo)三、社交媒体集成四、移动

HTML input 标签示例详解

《HTMLinput标签示例详解》input标签主要用于接收用户的输入,随type属性值的不同,变换其具体功能,本文通过实例图文并茂的形式给大家介绍HTMLinput标签,感兴趣的朋友一... 目录通用属性输入框单行文本输入框 text密码输入框 password数字输入框 number电子邮件输入编程框

HTML img标签和超链接标签详细介绍

《HTMLimg标签和超链接标签详细介绍》:本文主要介绍了HTML中img标签的使用,包括src属性(指定图片路径)、相对/绝对路径区别、alt替代文本、title提示、宽高控制及边框设置等,详细内容请阅读本文,希望能对你有所帮助... 目录img 标签src 属性alt 属性title 属性width/h

CSS3打造的现代交互式登录界面详细实现过程

《CSS3打造的现代交互式登录界面详细实现过程》本文介绍CSS3和jQuery在登录界面设计中的应用,涵盖动画、选择器、自定义字体及盒模型技术,提升界面美观与交互性,同时优化性能和可访问性,感兴趣的朋... 目录1. css3用户登录界面设计概述1.1 用户界面设计的重要性1.2 CSS3的新特性与优势1.