uniapp小程序多线程 Worker 实战【2024】

需求

最近遇到个小程序异步解码的需求,采用了WebAssembly,涉及大量的计算。由于小程序的双线程模型只有一个线程处理数据,因此智能寻求其它的解决方案。查看小程序的文档,发现小程序还提供一个异步线程的Worker方案,可以并行的。于是尝试采用Worker来进行异步运算,看了下文档,貌似只能有一个Worker异步进行,但是聊胜于无,能多一个线程并行计算,页面逻辑不会卡住就已经很不错了。

由于本人采用uniapp来进行小程序开发,由于引入了uniapp编译,导致整个开发过程更加复杂,本文记录了本人采用uni-best框架使用Worker过程遇到的一排深坑以及爬坑方案

小广告

uni-best不愧为2024年最佳的uni-app开发框架,uniapp+vue3+ts+unocss+uni-helper,typescript语言,体验极致的开发效率。本次项目就是在unibest生成的项目里进行uniapp开发

unibest最好用的 uniapp 开发模板icon-default.png?t=N7T8https://codercup.github.io/unibest-docs/

整合过程

下面按照官方的整合过程走一遍

创建目录

在src项目下创建workers目录,并建立index.js。这里是坑A,注意小程序Worker的引入必须显式的指定为.js,因此即使ts能够自动的编译为js,但是由于书写的原因。index文件必须为javascript而不是typeScript,但是index.js再引入的文件,是可以使用typescript格式的

引入文件

在页面采用一个按钮,点击开始进行异步的计算。按钮点击的代码如下:

在App.vue onShow里初始化

onShow(() => {const createNewWorker = () => wx.createWorker('workers/index.js', { useExperimentalWorker: true }) // 开启编码多线程let worker = createNewWorker()worker.onProcessKilled(() => {// 重新创建一个workerworker = createNewWorker()})
})

按照微信的文档,在某些情况下异步线程会被系统杀死。因此在这里采用了开启useExperimentalWorker保活机制

编写调用

下面按照微信官方的说明结合本人的项目开始编写

异步线程接收事件

下一步开始编写index.js,开启异步线程接口

worker.onMessage((obj) => {if (obj.event === 'add') {worker.postMessage({ event: 'addResult', data: obj.data.a + obj.data.b })}
})

解释下为什么这样写:

因为worker的调用是采用统一的调用接口,因此需要设计自己的消息格式,本人的消息格式设计如下

export interface IWorkderMessage {event: stringparams: any
}

event承载不同的消息给Worker,这样Worker可以做不同的事情。这里的例子只使用一个简单的调用,把消息参数里的a和b在异步线程相加,然后返回给主线程相加的结果

主线程发起事件

主线程的调用,在本人的结构里是采用mitt全局消息模型的,这样在统一的入口注册后。任何单元代码的任何地方都可以随时对异步线程发起调用。

  utils.on(Global.CC_WORKER_MESSAGE, (data: IWorkderMessage) => {worker.postMessage(data)})

页面发起异步调用

function doWorker() {utils.emit(Global.CC_WORKER_MESSAGE, { event: 'add', data: { a: 2, b: 3 } })
}

按微信官方的说法,在worker.onMessage里打印到console,理应看到输出(实际不是)。姑且先不管运行的结果,我们先按微信官方文档说明把代码写完。

主线程接收异步线程结果

主线程同样是采用worker.onMessage来接收异步线程的返回结果。我们加入到startWorker方法里,写成这样

onShow(() => {const createNewWorker = () => wx.createWorker('workers/index.js', { useExperimentalWorker: true }) // 开启编码多线程let worker = createNewWorker()worker.onProcessKilled(() => {// 重新创建一个workerworker = createNewWorker()})worker.onMessage((obj: Record<string, any>) => {// 异步线程全局消息转发utils.emit(obj.event, obj.data)})})

这里的utils.emit是我引入mitt后的全局消息模式,这样可以把返回的消息通过全局消息模型转发到对应的页面里

说明下这里为什么obj类型用Record<string,any>而不是IWorkderMessage,因为在小程序定义的d.ts里,已经把类型定义为Record,因此只能这样写

然后在对应界面写个全局的事件接收,这里仅打印下接收结果

  utils.on('addResult', (c) => {console.log(`addResult is ${c}`)})

坑来了

坑B

[worker] Uncaught Error: module 'workers/index.js' is not defined, require args is 'workers/index.js'

看到这里本人起初也是一头雾水的,啥叫index.js没定义,需要index.js。经过了一圈排查,才发现。我的编译后的dist\dev\mp-weixin目录里,没有workers目录!心态炸了,这叫什么错误,其它的文件都在,为毛单对workers过不去?

时间一分一秒过去,经过数小时冷静后。突然想到一个问题,vue3默认开启了Tree Shaking来优化代码,是不因为编译优化不认识worker机制,把从workders入口开始的整个代码链给弄丢了呢?按腾讯文档说,worker代码独立运行,会自动从createWorker开始运行,实时不是TreeShaking不认这一套,没代码调用的模块全部扫出家门了呢。之前require引入代码也不认,TreeShaking也给弄丢了,估计也是一个德行。

想完说干就干,修改下workers/index.js,做个简单的默认导出

worker.onMessage((obj) => {if (obj.event === 'add') {worker.postMessage({ event: 'addResult', data: obj.data.a + obj.data.b })}
})export default 'workers'

然后在App.vue导入,啥其它都不干,就打印下,这下编译器应该认为该模块是有用的吧

import workers from '@/workers'
console.log(workers)

然后开启调试,内牛满面,workers目录出现了,遗憾的是,继续出现错误了

坑C

估计很多人爬到这里,就会爬不动了。小程序上还是显示错误

app.js错误:
 Error: module 'workers/index.js' is not defined, require args is './workers/index.js'

看起来错误和前面的一样,但是仔细看又不一样。前面的是worker报错,是在启动worker的时候找不到模块,这里是app.js错误,而且仔细看是./workers/index.js找不到。那这个'workers/index.js' is not defined又是哪门子毛病呢?

经过数小时排查,发现编译后的app.js有这样一句代码:

但是如果我修改为workers/index.js就直接编译报错了

在这个地方卡了数小时。各种方法试过,一气之下想既然导入不对,干脆不要导入算了。于是把编译后的app.js的c=require("./workers/index.js")直接修改为c="hahaha",然后直接导入小程序模拟器运行。竟然成功了!

也就是说,对于最终编译的app.js,如果我把坑B产生的代码在最终编译结果去掉的话,代码就可以正常运行了。TMD VUE,TMD编译器优化!!!

但是不能每次都这样每编一次手动改一次呀,还不得把人累死,于是有了下一步

自动处理导入

既然是在编译阶段处理,那么我们应该是可以通过插件解决的,例如scss等插件都是可以对最终结果进行处理。于是想自己写个插件,对于从没写过插件的我来说难度又上了一个等级,幸好有GPT帮助,在折磨一阵子GPT后,再参考下其他类似代码。于是有了这个插件:

图片里vite.config.ts里的代码(顶部记得import fs from 'node:fs'):

      process.env.UNI_PLATFORM === 'mp-weixin' && {name: 'fix-uni-app-workers',apply: 'build',async closeBundle() {const buildType = process.env.NODE_ENV === 'development' ? 'dev' : 'build'const filePath = path.resolve(__dirname, `./dist/${buildType}/mp-weixin/app.js`) // 由app.js引入,修复这个即可fs.readFile(filePath, 'utf8', (err, data) => {if (err) {console.error('Error reading file:', err)return}console.log(`patch ${filePath}`)const result = data.replace(/require\("\.\/workers\/[a-zA-z-_]+\.js"\)/g, '""')// 写回文件fs.writeFile(filePath, result, 'utf8', (err) => {if (err) {console.error('Error writing file:', err)}})console.log('uniapp 小程序 worker 补丁完毕')})}}

解释下这个插件干了啥。

它在判断微信编译时(留着以后H5可以用编译开关写页面的Worker)开启,对编译目标目录的app.js进行处理。因此你的引用代码必须写到app.js。即

import workers from '@/workers'
console.log(workers)

这个是写在App.vue的,写到其它文件别怪我没提醒

然后对生成的文件做替换,把里面所有引入的js文件入口

=require("./workers/XXXXX.js")都替换成了="",这样都是打印空字符串,不会报错

对于workders里其它文件,也需要在app.js里通过写console.log的 方式注册,否则还是会出诡异的require args报错,这个正则把所有workers里的引入都替换成了常量字符

这样uniapp使用小程序的Workers就可以正常工作了🎉🎉🎉

按钮调用:

function doWorker() {utils.emit(Global.CC_WORKER_MESSAGE, { event: 'add', data: { a: 2, b: 3 } })
}

日志打印:

功能已经正常!!!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

联想Y410P跑大模型

安装vs 2017 查看GPU版本 查看支持哪个版本的cuda windows cuda更新教程_cuda 12.0-CSDN博客 下载并安装cuda tookit 10.1 CUDA Toolkit 10.1 Update 2 Archive | NVIDIA Developer 找到下载的文件&#xff0c;安装 参考安装链接 Win10 Vs2017 CUDA10.1安装&#xff08;避坑…

InnoDB存储引擎非常重要的一个机制--MVCC(多版本并发控制)

Mysql是如何实现隔离性的&#xff1f;&#xff08;锁MVCC&#xff09; 隔离性是指一个事务内部的操作以及操作的数据对正在进行的其他事务是隔离的&#xff0c;并发执行的各个事务之间不能相互干扰。隔离性可以防止多个事务并发执行时&#xff0c;可能存在交叉执行导致数据的不…

安全U盘和普通U盘有什么区别?

安全U盘&#xff08;也称为加密U盘或安全闪存驱动器&#xff09;与普通U盘肯定是有一些区别的&#xff0c;从字面意思上来看&#xff0c;就能看出&#xff0c;安全U盘是能够保护文件数据安全性的&#xff0c;普通U盘没这一些功能的&#xff0c;可随意拷贝文件&#xff0c;不防盗…

面试4:c++(数位物联)

1.const 关健字的作用 定义常量&#xff0c;防止变量被意外修改&#xff0c;增强程序的可读性和维护性。 可以用于指针&#xff0c;声明指向常量的指针或常量指针。 2.static关健字的作用 (1)在函数内&#xff0c;用于修饰局部变量&#xff0c;使其生命周期延长到整个程序运行期…

一文了解UVLED线光源的应用

在机器视觉系统中&#xff0c;光源作为不可或缺的一部分&#xff0c;能够提高目标成像效果&#xff0c;增强检测效果。光源的选择至关重要&#xff0c;选到不合适的会影响成像及检测效果。针对不同的检测对象,不同的形状光源应运而生。我们来看看最UVLED线光源。 下面以CCS的光…

zoomeye api报错 request invalid, validate usage and try again

项目场景&#xff1a; 调用zoomeye的api接口进行数据拿取 问题描述 之前接口一直通着今天突然报错&#xff0c;以下为源代码 pip install zoomeye from zoomeye.sdk import ZoomEye zm ZoomEye(api_key"34A8B452-D874-C63E0-8471-F3D4f89766f") zm.dork_search(a…

图片像素缩放,支持个性化自定义与精准比例调整,让图像处理更轻松便捷!

图片已经成为我们生活中不可或缺的一部分。无论是社交媒体的分享&#xff0c;还是工作文档的编辑&#xff0c;图片都扮演着至关重要的角色。然而&#xff0c;你是否曾经遇到过这样的问题&#xff1a;一张高清大图在上传时却受限于平台的大小要求&#xff0c;或者一张小图需要放…

tkinter+火山引擎+python实现语音识别聊天机器人

想要做一款能通过语音识别来聊天的智能机器人,首先需要能通过麦克风录制语音进行识别转换成文字,将文字发送给机器人得到聊天结果,并能将返回的文字转换成语音进行合成,之后再通过本地播放语音实现语音交互。 架构: 实现步骤 一、本地录音 本地录音可以通过pyAudio库实…

2024-06-05-记一次cnvd渗透

前言&#xff1a;挖src挖郁闷了&#xff0c;闲来无事选择挖一个cnvd来练练手&#xff0c;本次的漏洞都没啥难度&#xff0c;企查查资产过了5000万 说一下cnvd证书的下放标准 对于中危及中危以上通用型漏洞&#xff08;CVSS2.0基准评分超过4.0分&#xff09;&#xff0c;以及涉…

红酒:红酒保存中的光照与避免阳光直射

在红酒保存中&#xff0c;光照是一个常常被忽视的因素。光照对红酒的影响是不可小觑的&#xff0c;因为阳光中的紫外线会加速红酒的氧化&#xff0c;导致其口感和品质的下降。因此&#xff0c;在保存云仓酒庄雷盛红酒时&#xff0c;应特别注意避免阳光直射。 阳光直射对红酒的影…

企业代码签名证书1300元

随着手机和电脑等设备的普及&#xff0c;越来越多的开发者进入软件行业&#xff0c;为了软件的安全性、完整性和可信度&#xff0c;开发者往往会使用由正规CA认证机构颁发的代码签名证书对软件代码进行数字签名&#xff0c;来标识软件的来源和软件开发者的真实身份。今天就随SS…

博物馆文物库房管理软件

博物馆作为文化遗产的守护者和传承者&#xff0c;承载着人类智慧与文明的结晶。在博物馆的背后&#xff0c;一个庞大而严密的管理系统支撑着文物的保护与展示。而其中&#xff0c;文物库房管理软件的使用&#xff0c;无疑是一项重要的管理工具。 文物库房管理软件的功能具有多样…

【CentOS 7】挑战探索:在CentOS 7上实现Python 3.9的完美部署指南

【CentOS 7】挑战探索&#xff1a;在CentOS 7上实现Python 3.9的完美部署指南 大家好 我是寸铁&#x1f44a; 总结了一篇【CentOS 7】挑战探索&#xff1a;在CentOS 7上实现Python 3.9的完美部署指南详细步骤✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 此篇教程只适用于p…

【Mybatis】源码分析-高级应用

1、Mybatis配置文件深入理解 1.2、动态SQL语句 Mybatis 的映射⽂件中&#xff0c;前⾯我们的 SQL 都是⽐较简单的&#xff0c;有些时候业务逻辑复杂时&#xff0c;我们的 SQL是动态变化的&#xff0c;此时在前⾯的学习中我们的 SQL 就不能满⾜要求了。 1.2.1、条件判断 我们根…

技巧:合并ZIP分卷压缩包

如果ZIP压缩文件文件体积过大&#xff0c;大家可能会选择“分卷压缩”来压缩ZIP文件&#xff0c;那么&#xff0c;如何合并zip分卷压缩包呢&#xff1f;今天我们分享两个ZIP分卷压缩包合并的方法给大家。 方法一&#xff1a; 我们可以将分卷压缩包&#xff0c;通过解压的方式…

E10:系统弹窗提示

效果– window.WeFormSDK.showMessage("这是一个E10的提示", 3, 2); const onClickCreate () > console.log("create"); const onClickSave () > console.log("save"); const onClickCancel () > dialogComponent?.destroy(); co…

Java四舍五入保留小数

这里介绍两种方法&#xff1a; package Book.jj.hh;import java.text.DecimalFormat; //使用DecimalFormat类 public class Demo1 {public static void main(String[] args) {double num 123.52631;DecimalFormat a new DecimalFormat("#.00"); //小数点后有几个0…

SpringCloud Gateway基础入门与使用实践总结

官网文档&#xff1a;点击查看官网文档 Cloud全家桶中有个很重要的组件就是网关&#xff0c;在1.x版本中都是采用的Zuul网关。但在2.x版本中&#xff0c;zuul的升级一直跳票&#xff0c;SpringCloud最后自己研发了一个网关替代Zuul&#xff0c;那就是SpringCloud Gateway一句话…

抖音账号永久封号后强制注销释放实名!一分钟教程方法公开

目前方法是可行的&#xff0c;不知道能保持多久&#xff01; 下载旧版本抖音&#xff1a;下载抖音6.8版本或5.8版本的老版本应用。 使用封禁手机号登录&#xff1a;使用已被永久封禁的手机号登录旧版本的抖音应用。 账号注销操作&#xff1a; 在设置中找到账号与安全的选项。…

从零开始发布你的第一个npm插件包并在多项目中使用

引言 在开源的世界里&#xff0c;每个人都有机会成为贡献者&#xff0c;甚至是创新的引领者。您是否有过这样的想法&#xff1a;开发一个解决特定问题的小工具&#xff0c;让她成为其他开发者手中的利器&#xff1f;今天&#xff0c;我们就来一场实战训练&#xff0c;学习如何将…