当前位置: 首页 > news >正文

批量任务导致页面卡死解决方案

需求背景

        需要基于高德地图展示海量点位(大概几万个),点位样式要自定义(创建DOM),虽然使用了聚合点,但初始化时仍需要将几万个点位的DOM结构都创建出来。

        这里补充一句,高德地图在2.0版本对这种方式进行了优化,但同时少了某些功能,我的需求要使用1.4版本的这种方式渲染。

问题及定位分析

        功能实现后,发现从开始加载点位,到点位出现的过程中,页面会卡死,无法响应用户交互,可以点击Demo的常规模式查看效果(实际业务下有更多逻辑,阻塞时间会更久)。

        可以看到,当我开始渲染点位后,点击输入框进行输入,是没有立即响应的,点位加载完后才会对之前的交互做响应。

问题分析

        其实从上面高德地图的点位渲染逻辑很容易想到主要是批量创建点位的DOM结构占用了主线程

        可以看到,批量的genMarker任务占用了大量时间,genMarker会在每次创建点位时执行一次,一次创建4w个点位,就会连续执行4w次。

// 生成点位,创建DOM自定义样式
genMarker(device) {const innerHTML = `<div class="camera"></div>`const size = [48, 49]const markerOffset = new AMap.Pixel(-size[0] / 2, -size[1] / 2)const marker = new AMap.Marker({position: device.lnglat,extData: device,size,})const container = document.createElement('div')container.className = 'map-marker'container.innerHTML = innerHTMLmarker.setContent(container)marker.setOffset(markerOffset)marker.selected = falsereturn marker
}

页面显示机制

        动的画面其实是由一帧一帧的静态图快速切换组成的,人眼的反应速度有限,当画面切换的够快,人眼看着就是连续的动画了。

        对于人眼来说,当每秒切换60张图片时,就会认为是连贯的。所以主流的显示器是60hz的,1s刷新60次,那么每16.7ms需要刷新一次,浏览器会自动适配这个频率,这时对应我们前端页面就是每16.7ms需要渲染一次。

        页面每隔16.7ms才会渲染一次,那么在两次渲染的中间时间,就是浏览器的空闲时间,在这段空闲时间执行的任务,是不会阻塞到页面渲染的流畅性的。反之,对于上面的案例,数万个genMarker在一个帧区间内连续的执行,下一帧一直不能渲染,页面看起来就被卡住了。

任务拆分

        对于大量的计算或许首先考虑的是Web Worker使其不占用主线程,但是由于要操作DOM,不适合当前场景。

        对于页面的流畅性来说,这些点位的创建属于「低优先级任务」。既然卡顿的原因是这些genMarker任务一个接一个的「连续」的在执行,一直占用着主线程,那么我们可以将这些批量的任务进行拆分,保证这些任务只在空闲时间执行。每次执行下一个任务的时候,先检查一下当前页面是否该渲染下一帧了,这时需要「把主线程让出来」,让页面进行渲染(了解react的人应该感觉很熟悉,思路来自react的Fiber)

requestIdleCallback

        「让出主线程」,关键的一点在于我们如何知道什么时候是空闲时间,什么时候空闲时间结束,该进行渲染了。requestIdleCallback就是浏览器提供给我们用来判断这个时机的api,它会在浏览器的空闲时间来执行传给它的回调函数。另外如果指定了超时时间,会在超时后的下一帧强制执行

const id = window.requestIdleCallback((deadline) => {// 当前帧剩余时间大于0,或任务已超时if(deadline.timeRemaining() > 0 || deadline.didTimeout) {// do somethingconsole.log(1)}
}, { timeout: 2000 }) // 指定超时时间// window.cancelIdleCallback(id) 与定时器类似,支持取消

        requestIdleCallback在Event Loop的执行时机如下图所示,蓝色区域代表一帧内的渲染任务,当这些任务执行完后,剩余的时间被认为是空闲时间

        以一个简单的任务(singlTask)为例,以常规模式连续执行2w次,全部执行完需要大概2s时间(依赖机器性能变化),这期间主线程被一直被占用,页面会被卡住。

function singleTask() {const now = performance.now()while (performance.now() - now < 0.001) { } // 模拟耗时操作,每次任务耗时约0.001ms
}const data = new Array(20000).fill(1)function normarlRun() {for (let i = 0; i < data.length; i++) {// 2w个任务连续执行singleTask(data[i])}result('done')
}

        对其使用requestIdleCallback进行拆分,只在空闲时间执行部分任务,若当前帧的空闲时间结束,则暂停批量任务,让出主线程:

function ridRun() {let i = 0let option = { timeout: 200 } // 任务超时时间function handler(idleDeadline) {while ((idleDeadline.timeRemaining() > 0 || idleDeadline.didTimeout) && i < data.length) {// 当前帧有剩余时间,或任务已等待超时强制执行singleTask(data[i++])}// idleDeadline.timeRemaining() === 0 当前帧已没有空闲时间,让出主线程if (i < data.length) {window.requestIdleCallback(handler, option) // 任务未执行完,继续等待下次空闲时间执行} else {result('done')}}window.requestIdleCallback(handler, option)
}

模拟requestIdleCallback

        不幸的是requestIdleCallback兼容性不够好,Safari完全不支持:

        参考react的实现,我们可以使用requestAnimationFrame和MessageChannel来模拟实现一个requestIdleCallback。requestAnimationFrame在每一帧开始渲染前执行(见上面的Event Loopt图),当帧开始渲染前,我们标记开始时间(start),并使用MessageChannel创建一个宏任务,根据上面的Event Loop流程,渲染完毕后,会执行刚才创建出的宏任务,这时在宏任务中对比标记的开始时间,是否超出了一帧的渲染时间(current - start > 16.7),来判断当前是否是空闲时间。

        setTimeout即使指定时间为0 浏览器实际也会延时几毫秒后才执行(chrome大概为4ms),因此使用MessageChannel而不是setTimeout来创建宏任务

        模拟requestIdleCallback的具体实现:

const genId = (function () {let id = 0return function () {return ++id}
})()const idMap: {[key: number]: number
} = {}const _requestIdleCallback: (cb: (idleDeadline: IdleDeadline) => void,options?: { timeout: number }
) => number = function (cb, options) {const channel = new MessageChannel()const port1 = channel.port1const port2 = channel.port2let deadlineTime: number // 超时时间let frameDeadlineTime: number // 当前帧的截止时间let callback: (idleDeadline: IdleDeadline) => voidconst id = genId()port2.onmessage = () => {const frameTimeRemaining = () => frameDeadlineTime - performance.now() // 获取当前帧剩余时间const didTimeout = performance.now() >= deadlineTime // 是否超时if (didTimeout || frameTimeRemaining() > 0) {const idleDeadline = {timeRemaining: frameTimeRemaining,didTimeout}callback && callback(idleDeadline)} else {idMap[id] = requestAnimationFrame((timeStamp) => {frameDeadlineTime = timeStamp + 16.7port1.postMessage(null)})}}idMap[id] = window.requestAnimationFrame((timeStamp) => {frameDeadlineTime = timeStamp + 16.7 // 当前帧截止时间,按照 60fps 计算deadlineTime = options?.timeout ? timeStamp + options.timeout : Infinity // 超时时间callback = cbport1.postMessage(null)})return id
}const _cancelIdleCallback = function (id: number) {if (!idMap[id]) returnwindow.cancelAnimationFrame(idMap[id])delete idMap[id]
}export const requestIdleCallback = window.requestIdleCallback || _requestIdleCallback
export const cancelIdleCallback = window.cancelIdleCallback || _cancelIdleCallback

使用requestIdleCallback拆分点位生成

        将genMarker批量任务进行拆分,只在空闲时间时间进行拆分:

addMarkersByRid() {cancelIdleCallback(this.ridId)const { markerList, points, genMarker, genCluster } = thislet index = 0const ridOption = { timeout: 20 }const handler = (idleDeadline) => {const { timeRemaining } = idleDeadline// 只在空闲时间生成点位while (timeRemaining() > 0 && index < points.length) {const device = points[index]const marker = genMarker(device)markerList.push(marker)index++}if (index < points.length) {this.ridId = requestIdleCallback(handler, ridOption)} else {console.log('done') // 全部点位生成完毕}}this.ridId = requestIdleCallback(handler, ridOption)
}

        可以看到,点位的渲染并没有再影响到页面的响应了

相关文章:

批量任务导致页面卡死解决方案

需求背景 需要基于高德地图展示海量点位&#xff08;大概几万个&#xff09;&#xff0c;点位样式要自定义&#xff08;创建DOM&#xff09;&#xff0c;虽然使用了聚合点&#xff0c;但初始化时仍需要将几万个点位的DOM结构都创建出来。 这里补充一句&#xff0c;高德地图在2.…...

避免“文献综抄”,5种写作结构助你完成文献综述→

很多作者可能有过这样的体验&#xff1a;读了很多文献&#xff0c;但在写综述的时候总感觉不像是在写文献综述&#xff0c;更像在写文献总结 如果引用方面不注意&#xff0c;甚至会成为文献综抄。 那么&#xff0c;你可以参考下我们整理的以下资料哦~ 01 文献总结和文献综述的…...

Java异常和反射

JAVA 异常分类及处理 概念 } final Entry<K,V> getEntryUsingComparator(Object key) { K k (K) key; // 获取该 TreeMap 的 comparator Comparator<? super K> cpr comparator; if (cpr ! null) { // 从根节点开始 Entry<K,V> p …...

Accesss数据库的那点事

Accesss数据库的那点事 1.Access的简介 Access&#xff08;全称为Microsoft Access&#xff09;是一个关系型数据库管理系统&#xff08;RDBMS&#xff09;。它是由微软公司开发的数据库软件&#xff0c;用于创建、管理和操作数据库应用程序。 Access提供了一个可视化的开发环…...

网络基础学习:osi网络七层模型

osi网络七层模型 什么是OSI&#xff0c;什么是ISO?为什么ISO要提出OSI网络七层模型&#xff1f;OSI七层的划分以及具体内容第七层 应用层第六层 表示层第五层 会话层第四层 传输层第三层 网络层第二层 数据链路层第一层 物理层 每一层与设备的对应关系 什么是OSI&#xff0c;什…...

EndNote X9 引用参考 单击文献编号,不能跳转到文尾文献列表处,咋解决?文献编号 不能跳转 ,怎么办?

文章目录 1 正常情况下 引用文献编号 是可以跳转的2 问题分析3 解决方法4 EndNote X9 插入参考文献常见问题总结5 EndNote X9 快速上手教程&#xff08;毕业论文参考文献管理器&#xff09; 1 正常情况下 引用文献编号 是可以跳转的 正确的插入文献后&#xff0c; 正常情况下&a…...

用免费蜜罐工具配置Modbus工控蜜罐

导语&#xff1a;本文将用DecoyMini免费蜜罐工具来配置自定义的ModbusTCP工控仿真模板&#xff0c;并介绍部署后的Modbus蜜罐的使用效果。 DecoyMini是一个免费的蜜罐工具&#xff0c;其特色是仿真能力采用与软件松耦合的仿真模板来进行管理。通过一键式导入云端仿真模板库里的…...

DataGridXL中快速搜索单元格和底部全屏模式区域隐藏

DataGridXL表格是在2020年发布&#xff0c;DataGridXL在设计时就考虑到了性能。提供最快、最简单、最可靠的数据网格。DataGridXL支持所有常用所有的浏览器&#xff0c;为 Web 应用程序提供类似于 Microsoft Excel 的体验&#xff0c;它支持前端框架有Vue、React、Angular等。 …...

DotNet几种微服务框架,你用过吗?

最近有群友问&#xff0c;.NET有哪些微服务框架&#xff1f;.NET的微服务框架还真不多&#xff0c;一般企业都会自己搭建微服务框架&#xff0c;或者基于其它框架搭建微服务&#xff08;比如abp&#xff09;。本文将介绍几种微服务框架&#xff0c;供大家学习参考。 一、Servi…...

Nature | 生成式人工智能如何构建更好的抗体

疫情高峰期&#xff0c;研究人员竞相开发一些首批有效的COVID-19治疗方法&#xff1a;从已经康复的人的血液中分离出来的抗体分子。 现在&#xff0c;科学家已经证明&#xff0c;生成式人工智能&#xff08;AI&#xff09;可以通过一些繁琐的过程提供捷径&#xff0c;提出增强抗…...

【hive】基于Qt5和libuv udp 的lan chat

作者已经不更新了,但是很棒 在线用户列表: 聊天窗口 主程序 单独的网络线程: network_thread data管理关联网络管理的 程序update升级更新 和消息收到 即可...

Java版本工程项目管理系统源码,助力工程企业实现数字化管理

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示…...

什么是零拷贝?

零拷贝 什么是零拷贝 零拷贝指的是&#xff0c;从一个存储区域到另一个存储区域的copy任务无需CPU参与就可完成。零拷贝的底层是 通过DMA总线技术实现的。零拷贝与具体的编程语言无关&#xff0c;完全依赖于OS&#xff0c;OS支持就可使用&#xff0c;不支持 设置了也不起作用…...

计算机专业含金量高的证书

目录 第一种证书&#xff1a;计算机技术与软件专业资格考试证书 第二种证书&#xff1a;微软认证 第三种证书&#xff1a;Oracle认证 第四种证书&#xff1a;思科认证 第五种证书&#xff1a;华为认证 第六种证书&#xff1a;红帽认证工程师 第七种证书&#xff1a;阿里…...

原装二手Keithley 2401低压源表 吉时利2401数字源表

Keithley 2401低压源表&#xff0c;20V&#xff0c;1A&#xff0c;20W Keithley 2401 低压源表提供精密电压和电流源和测量功能&#xff08;1V - 20V 和 10pA - 1A&#xff09;。它既是高度稳定的直流电源&#xff0c;又是真正的仪器级 5 位万用表。电源特性包括低噪声、精度和…...

gradle-8.1.1-all 快速下载百度网盘下载

Gradle 8.1.1 发布 这是Gradle 8.1的第一个补丁发布。 它修复了以下3个问题&#xff1a; 为配置缓存检测具有数千个lambdas的类时出现MethodTooLargeException&#xff1b;用Gradle 8.1构建的Kotlin DSL预编译脚本插件不能用于其他版本的Gradle&#xff1b;Gradle 8.1在buil…...

C#开发的OpenRA游戏之基地工程车部署命令产生过程

C#开发的OpenRA游戏之基地工程车部署命令产生过程 OpenRA游戏里,前面已经分析基地工程车部署的流程, 但那只是一个框架的流程,只能理解大体的框架, 如果想要深入一步了解怎么样产生部署命令,还需继续探讨以下的代码。 基地工程车是先选中,然后再在基地工程车上面点击右…...

C++ 智能指针的原理、分类、使用

1. 智能指针介绍 为解决裸指针可能导致的内存泄漏问题。如&#xff1a; a&#xff09;忘记释放内存&#xff1b; b&#xff09;程序提前退出导致资源释放代码未执行到。 就出现了智能指针&#xff0c;能够做到资源的自动释放。 2. 智能指针的原理和简单实现 2.1 智能指针的原…...

学习笔记——SVG.js中形状元素的创建及其相关方法

CreateElement 1&#xff09;创建svg元素 在svg.js中&#xff0c;每个元素都是一个对象&#xff0c;可以通过构造它来创建&#xff1a; import { Rect } from "svgdotjs/svg.js" var rect new Rect().size(100, 100).addTo(draw) // or to reuse an existing nod…...

Linux一学就会——系统文件I/O

Linux一学就会——系统文件I/O 有几种输出信息到显示器的方式 #include <stdio.h> #include <string.h> int main() {const char *msg "hello fwrite\n";fwrite(msg, strlen(msg), 1, stdout);printf("hello printf\n");fprintf(stdout, &q…...

OpenCV-Python图像阈值

目录 简单阈值 自适应阈值 Otsu的二值化 所谓的图像阈值&#xff0c;就是图像二值化&#xff0c;什么是二值化&#xff0c;就是只有0和1&#xff0c;没有其他的。在OpenCV的图像里面&#xff0c;二值化表示图像的像素为0和255&#xff0c;并没有其他的值&#xff0c;它跟灰度…...

LangChain-Agents 入门指南

LangChain-Agents 入门指南 LangChain-Agents 入门指南注册 Serpapi运行高级 Agents API 测试运行 Google Search其它 Here’s the table of contents: LangChain-Agents 入门指南 LangChain是一个使用LLMs构建应用程序的工具箱&#xff0c;包含Models、Prompts、Indexes、Mem…...

深度学习-tensorflow 使用keras进行深度神经网络训练

概要 深度学习网络的训练可能会很慢、也可能无法收敛&#xff0c;本文介绍使用keras进行深度神经网络训练的加速技巧&#xff0c;包括解决梯度消失和爆炸问题的策略&#xff08;参数初始化策略、激活函数策略、批量归一化、梯度裁剪&#xff09;、重用预训练层方法、更快的优化…...

【NLP开发】Python实现聊天机器人(ChatterBot,集成前端页面)

&#x1f37a;NLP开发系列相关文章编写如下&#x1f37a;&#xff1a; &#x1f388;【NLP开发】Python实现词云图&#x1f388;&#x1f388;【NLP开发】Python实现图片文字识别&#x1f388;&#x1f388;【NLP开发】Python实现中文、英文分词&#x1f388;&#x1f388;【N…...

Python 操作 Excel,如何又快又好?

➤数据处理是 Python 的一大应用场景&#xff0c;而 Excel 则是最流行的数据处理软件。因此用 Python 进行数据相关的工作时&#xff0c;难免要和 Excel 打交道。Python处理Excel 常用的系列库有&#xff1a;xlrd、xlwt、xlutils、openpyxl ◈xlrd &#xff0d; 用于读取 Exce…...

Spring Redis 启用TLS配置支持(踩坑解决)

由于线上Redis要启用TLS,搜遍了google百度也没一个标准的解决方案,要不这个方法没有,要不那个类找不到...要不就是配置了还是一直连不上redis.... 本文基于 spring-data-redis-2.1.9.RELEASE 版本来提供一个解决方案: 1.运维那边提供过来三个文件,分别是redis.crt redis.key …...

centOS7忘记登录密码该如何重新修改登录密码

文章目录 前言一、重新修改登录密码1.1、第一步1.2、第二步1.3、第三步1.4、第四步1.5、第五步1.6、第六步1.7、第七步1.8、第八步 前言 忘记密码并不可怕&#xff0c;只要学会方法&#xff0c;密码随时都可以找回。 一、重新修改登录密码 1.1、第一步 当打开centOS7之后忘记…...

揭开基于 AI 的推荐系统的神秘面纱:深入分析

人工智能 &#xff08;AI&#xff09; 以多种方式渗透到我们的生活中&#xff0c;使日常任务更轻松、更高效、更个性化。人工智能最重要的应用之一是推荐系统&#xff0c;它已成为我们数字体验不可或缺的一部分。从在流媒体平台上推荐电影到在电子商务网站上推荐产品&#xff0…...

MySQL的事务特性、事务特性保证和事务隔离级别

事务是指要么所有的操作都成功执行&#xff0c;要么所有的操作都不执行的一组数据库操作。 一、MySQL提供了四个事务特性&#xff0c;即ACID&#xff1a; 1. 原子性&#xff08;Atomicity&#xff09;&#xff1a;一个事务中的所有操作要么全部提交成功&#xff0c;要么全部回…...

shell脚本----函数

文章目录 一、函数的定义1.1 shell函数:1.2函数如何定义 二、函数的返回值三、函数的传参四、函数变量的作用范围五、函数的递归六、函数库 一、函数的定义 1.1 shell函数: 使用函数可以避免代码重复使用函数可以将大的工程分割为若干小的功能模块&#xff0c;代码的可读性更…...