React中的 Scheduler

为什么需要调度

在 React 中,组件最终体现为 Fiber,并形成 FiberTree,Fiber 的目的是提高渲染性能,将原先的 React 渲染任务拆分为多个小的微任务,这样做的目的是可以灵活的让出主线程,可以随时打断渲染,后面再继续执行。由于需要让出主线程,需要将任务保存起来,一个个排队执行,需要的时候进行切换,为了实现排队、切换,就需要实现一个调度引擎,哪个任务先执行,哪个任务后执行,暂停之后怎么继续执行。

优先级队列

React 中,Scheduler.js 定义了定义了调度的核心逻辑,Task 包存在一个 PriorityQueue 中,优先级高的任务会先进行处理。

WorkLoop

React 中,WorkLoop 是指循环Fiber Tree 去找需要处理的任务,WorkLoop 分为同步和并发,这两个逻辑几乎一样,并发处理是,要检查是否要暂停并释放主线程给浏览器执行其他任务。

## 同步,不能中断
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}
function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

shouldYield 大于 5 毫秒就会释放,打开 React Tools Profile 可以看到这个渲染过程

function shouldYieldToHost() {
  const timeElapsed = getCurrentTime() - startTime;
  if (timeElapsed < frameInterval) {
    // The main thread has only been blocked for a really short amount of time;
    // smaller than a single frame. Don't yield yet.
    return false;
  }

在这里插入图片描述

调度

首先,调度创建任务,任务通过 callback function 创建,源码 Sheduler.js -> unstable_scheduleCallback,可以看到,优先级是通过过期时间进行定义的,越早过期的优先级越高,正常的任务 5 秒过期,USER_BLOCKING_PRIORITY_TIMEOUT 阻碍用户操作的任务 250 毫秒。


  var timeout;
  switch (priorityLevel) {
    case ImmediatePriority:
      timeout = IMMEDIATE_PRIORITY_TIMEOUT;
      break;
    case UserBlockingPriority:
      timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
      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;

  var newTask = {
    id: taskIdCounter++,
    callback,
    priorityLevel,
    startTime,
    expirationTime,
    sortIndex: -1,
  };

任务加入队列并执行

  if (startTime > currentTime) {
    // This is a delayed task.
    newTask.sortIndex = startTime;
    ##########加入队列
    push(timerQueue, newTask);
    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
      // All tasks are delayed, and this is the task with the earliest delay.
      if (isHostTimeoutScheduled) {
        // Cancel an existing timeout.
        cancelHostTimeout();
      } else {
        isHostTimeoutScheduled = true;
      }
      // Schedule a timeout.
      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  } else {
    newTask.sortIndex = expirationTime;
	##########加入队列
    push(taskQueue, newTask);
    if (enableProfiling) {
      markTaskStart(newTask, currentTime);
      newTask.isQueued = true;
    }
    // Schedule a host callback, if needed. If we're already performing work,
    // wait until the next time we yield.
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);
    }

requestHostCallback 最终会调用 schedulePerformWorkUntilDeadline,schedulePerformWorkUntilDeadline会调用 performWorkUntilDeadline。

### 调用 schedulePerformWorkUntilDeadline
function requestHostCallback(callback) {
  scheduledHostCallback = callback;
  if (!isMessageLoopRunning) {
    isMessageLoopRunning = true;
    schedulePerformWorkUntilDeadline();
  }
}
## 最终调用 performWorkUntilDeadline
let schedulePerformWorkUntilDeadline;
if (typeof localSetImmediate === 'function') {
  // Node.js and old IE.
  // There's a few reasons for why we prefer setImmediate.
  //
  // Unlike MessageChannel, it doesn't prevent a Node.js process from exiting.
  // (Even though this is a DOM fork of the Scheduler, you could get here
  // with a mix of Node.js 15+, which has a MessageChannel, and jsdom.)
  // https://github.com/facebook/react/issues/20756
  //
  // But also, it runs earlier which is the semantic we want.
  // If other browsers ever implement it, it's better to use it.
  // Although both of these would be inferior to native scheduling.
  schedulePerformWorkUntilDeadline = () => {
    localSetImmediate(performWorkUntilDeadline);
  };
} else if (typeof MessageChannel !== 'undefined') {
  // DOM and Worker environments.
  // We prefer MessageChannel because of the 4ms setTimeout clamping.
  const channel = new MessageChannel();
  const port = channel.port2;
  channel.port1.onmessage = performWorkUntilDeadline;
  schedulePerformWorkUntilDeadline = () => {
    port.postMessage(null);
  };
} else {
  // We should only fallback here in non-browser environments.
  schedulePerformWorkUntilDeadline = () => {
    localSetTimeout(performWorkUntilDeadline, 0);
  };
}

scheduledHostCallback(hasTimeRemaining, currentTime) ,通过上面的代码的定义可知scheduledHostCallback 就是flushWork,所以这里是调用 flushWork(hasTimeRemaining, currentTime)

const performWorkUntilDeadline = () => {
  if (scheduledHostCallback !== null) {
    const currentTime = getCurrentTime();
    // Keep track of the start time so we can measure how long the main thread
    // has been blocked.
    startTime = currentTime;
    const hasTimeRemaining = true;

    // If a scheduler task throws, exit the current browser task so the
    // error can be observed.
    //
    // Intentionally not using a try-catch, since that makes some debugging
    // techniques harder. Instead, if `scheduledHostCallback` errors, then
    // `hasMoreWork` will remain true, and we'll continue the work loop.
    let hasMoreWork = true;
    try {
      hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
    } finally {
      if (hasMoreWork) {
        // If there's more work, schedule the next message event at the end
        // of the preceding one.
        schedulePerformWorkUntilDeadline();
      } else {
        isMessageLoopRunning = false;
        scheduledHostCallback = null;
      }
    }
  } else {
    isMessageLoopRunning = false;
  }
  // Yielding to the browser will give it a chance to paint, so we can
  // reset this.
  needsPaint = false;
};

flushWork 在调用 workLoop, workLoop 是 scheduler 核心

function flushWork(hasTimeRemaining, initialTime) {
  // 省略一部分代码
  isPerformingWork = true;
  const previousPriorityLevel = currentPriorityLevel;
  try {
    if (enableProfiling) {
      try {
        return workLoop(hasTimeRemaining, initialTime);

workLoop 是 Scheduler 的核心方法,当 currentTask 的 callback 是一个方法,这部分处理暂停任务,如果callback返回一个 function,那么在一次执行中还是执行当前任务,否则任务完成。

 const callback = currentTask.callback;
    if (typeof callback === 'function') {
      currentTask.callback = null;
      currentPriorityLevel = currentTask.priorityLevel;
      const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
      if (enableProfiling) {
        markTaskRun(currentTask, currentTime);
      }
      const continuationCallback = callback(didUserCallbackTimeout);
      currentTime = getCurrentTime();
      //**************是否是暂停任务
      if (typeof continuationCallback === 'function') {
        currentTask.callback = continuationCallback;
        if (enableProfiling) {
          markTaskYield(currentTask, currentTime);
        }
      } else {
        if (enableProfiling) {
          markTaskCompleted(currentTask, currentTime);
          currentTask.isQueued = false;
        }
        if (currentTask === peek(taskQueue)) {
          pop(taskQueue);
        }
      }
      advanceTimers(currentTime);
    } else {
      pop(taskQueue);
    }

总结

React 调度是用来调度 fiber 任务的,任务可以暂停并将控制权交给浏览器,调度的核心是通过 Priority Queue 实现,根据重要程度进行排序,过期时间作为排序字段,先到期的先执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/697169.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ffmpeg安装和简单使用

Ffmpeg安装 下载并解压 进入官网 (https://ffmpeg.org/download.html)&#xff0c;选择 Window 然后再打开的页面中下滑找到 release builds&#xff0c;点击 zip 文件下载 环境变量配置 下载好之后解压&#xff0c;找到 bin 文件夹&#xff0c;里面有3个 .exe 文件 然后复制…

高德地图简单实现点标,和区域绘制

高德地图开发文档:https://lbs.amap.com/api/javascript-api/guide/abc/quickstart 百度搜索高德地图开发平台 注册高德地图开发账号 在应用管理中 我的应用中 添加一个Key 点击提交 进入高德地图开发文档:https://lbs.amap.com/api/javascript-api/guide/abc/quickstart …

CTE-6作文

第一段 现象 引出原因 第二段 感受 举例 意义 危害 第三段 建议 展望

使用MFC DLL

本文仅供学习交流&#xff0c;严禁用于商业用途&#xff0c;如本文涉及侵权请及时联系本人将于及时删除 应用程序与DLL链接后&#xff0c;DLL才能通过应用程序调用运行。应用程序与DLL链接的方式主要有如下两种&#xff1a;隐式链接和显式链接。 隐式链接又称为静态加载&…

【python】python化妆品销售logistic逻辑回归预测分析可视化(源码+课程论文+数据集)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

Apache Pulsar 从入门到精通

一、快速入门 Pulsar 是一个分布式发布-订阅消息平台&#xff0c;具有非常灵活的消息模型和直观的客户端 API。 最初由 Yahoo 开发&#xff0c;在 2016 年开源&#xff0c;并于2018年9月毕业成为 Apache 基金会的顶级项目。Pulsar 已经在 Yahoo 的生产环境使用了三年多&#…

AI服务器相关知识

在当今社会&#xff0c;人工智能的应用场景愈发广泛&#xff0c;如小爱同学、天猫精灵等 AI 服务已深入人们的生活。随着人工智能时代的来临&#xff0c;AI 服务器也开始在社会各行业发挥重要作用。那么&#xff0c;AI 服务器与传统服务器相比&#xff0c;究竟有何独特之处&…

速度位置规划实现精确定位的问题

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

npm install 的原理

1. 执行命令发生了什么 &#xff1f; 执行命令后&#xff0c;会将安装相关的依赖&#xff0c;依赖会存放在根目录的node_modules下&#xff0c;默认采用扁平化的方式安装&#xff0c;排序规则为&#xff1a;bin文件夹为第一个&#xff0c;然后是开头系列的文件夹&#xff0c;后…

汇聚荣科技有限公司实力怎么样?

汇聚荣科技有限公司&#xff0c;一家专注于高新技术研发和应用的企业&#xff0c;在业界享有一定的声誉。那么&#xff0c;这家公司的实力究竟如何?我们将从公司概况、技术研发、市场表现、企业文化和未来展望五个方面进行详细探讨。 一、公司概况 汇聚荣科技有限公司经过多年…

Linux系统编程(十一)线程、线程控制

线程 一、线程概念&#xff1a; ps -eLf 查看线程号&#xff08;cpu 执行的最小单位&#xff09; 二、Linux内核线程实现原理 三、三级映射&#xff08;三级页表&#xff09; 进程PCB-->页面&#xff08;可看成数组&#xff0c;首地址位于PCB中&#xff09;--》页表--》页…

Silanna UV光荣推出了一款革命性的高功率远紫外线LED

这款令人瞩目的光源&#xff0c;拥有令人震撼的235nm波长&#xff0c;并被巧妙地封装在紧凑的6.8mm结构中&#xff0c;其魅力与实力兼具。 今年六月&#xff0c;在苏格兰圣安德鲁斯大学举行的盛大2024年远紫外科学和技术国际大会&#xff08;ICFUST&#xff09;上&#xff0c;S…

C# BindingSource 未完BindingNavigator

数据绑定导航事件数据验证自定义示例示例总结 在 C#中&#xff0c; BindingSource 是一个非常有用的控件&#xff0c;它提供了数据绑定的基础设施。 BindingSource 允许开发者将数据源&#xff08;如数据库、集合、对象等&#xff09;与用户界面控件&#xff08;如文本框、下…

集成学习模型对比优化—银行业务

1.Data Understanding 2.Data Exploration 3.Data Preparation 4.Training Models 5.Optimization Model 集成学习模型对比优化—银行业务 1.Data Understanding import pandas as pd from matplotlib import pyplot as plt import seaborn as sns df pd.read_csv(&quo…

《TCP/IP网络编程》(第十四章)多播与广播

当需要向多个用户发送多媒体信息时&#xff0c;如果使用TCP套接字&#xff0c;则需要维护与用户数量相等的套接字&#xff1b;如果使用之前学习的UDP&#xff0c;传输次数也需要和用户数量相同。 所以为了解决这些问题&#xff0c;可以采用多播和广播技术&#xff0c;这样只需要…

pxe自动装机:

pxe自动装机&#xff1a; 服务端和客户端 pxe c/s模式&#xff0c;允许客户端通过网络从远程服务器&#xff08;服务端&#xff09;下载引导镜像&#xff0c;加载安装文件&#xff0c;实现自动化安装操作系统。 无人值守 无人值守&#xff0c;就是安装选项不需要人为干预&am…

当前 Python 版本中所有保留字keyword.kwlist

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 当前 Python 版本中 所有保留字 keyword.kwlist [太阳]选择题 根据给定的Python代码&#xff0c;哪个选项是正确的&#xff1f; import keyword print("【执行】keyword.kwlist"…

vue面试题2-根据以下问题回答

以下是针对提供的关于Vue的问题的回答&#xff1a; Vue的基本原理&#xff1a; Vue.js是一个流行的JavaScript框架&#xff0c;用于构建用户界面和单页面应用。其基本原理包括响应式数据、模板、组件系统、指令、生命周期钩子和虚拟DOM。 双向数据绑定的原理&#xff1a; Vue通…

自动化测试-Selenium(一),简介

自动化测试-Selenium 1. 什么是自动化测试 1.1 自动化测试介绍 自动化测试是一种通过自动化工具执行测试用例来验证软件功能和性能的过程。与手动测试不同&#xff0c;自动化测试使用脚本和软件来自动执行测试步骤&#xff0c;记录结果&#xff0c;并比较预期输出和实际输出…

第十一篇——信息增量:信息压缩中的保守主义原则

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 通过信息中的保守主义&#xff0c;我想到了现实中人的保守主义一样&#…