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

news2024/11/22 6:49:57

需求

最近遇到个小程序异步解码的需求,采用了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(() => {
    // 重新创建一个worker
    worker = 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: string
  params: 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(() => {
    // 重新创建一个worker
    worker = 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.coloradmin.cn/o/1794748.html

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

相关文章

暗黑系短视频:成都鼎茂宏升文化传媒公司

暗黑系短视频&#xff1a;探索未知的视觉艺术 在短视频盛行的今天&#xff0c;各种风格和主题的作品层出不穷&#xff0c;其中&#xff0c;暗黑系短视频以其独特的魅力和深度&#xff0c;成都鼎茂宏升文化传媒公司吸引了众多观众的关注。这类视频往往带有一种神秘、压抑的氛围…

李良济中医课堂,助力市民健康,传播中医药文化,坚守初心扬国粹!

为了更好地传承并发扬中医文化&#xff0c;传播健康理念&#xff0c;提高民众预防疾病的意识&#xff0c;让广大市民感受到中医药就在我身边。 李良济中医课堂&#xff0c;积极走进老年大学&#xff0c;青年夜校&#xff0c;同时主动加入萌趣中草药的活动等&#xff0c;为市民群…

小程序集arcgis地图显示自定义坐标的功能实现记录!(学习笔记)

最近再做一个新能源回收项目&#xff0c;项目中有个根据回收点坐标数据显示区域内回收点位置&#xff0c;点击图标直接导航到该位置&#xff0c;及分布的需求&#xff0c;研究了一下&#xff0c;实现效果如下&#xff0c;实现起来很简单&#xff0c;代码及效果 回收点位置及分…

在编程Python的时候发生ModuleNotFoundError: No module named distutils报错怎么办

1.先查看Python版本 首先我们先去打开终端就是先widr再输入cmd 然后进去在输入Python -V要注意大小写 我的版本是3.9.7版本但是我使用的PyCharm 是 2021.1.1 x64版本没有办法主动去识别因为这个版太低了你的Python版本很高所以无法识别 2.解决方法 只需要把你的Python现版…

使用手机小程序给证件照换底色

临时遇到一个需求&#xff0c;需要给证件照换底色。原始图像如下 最终需要换成红底的。 本次使用一款小程序&#xff02;泰世茂证件照&#xff02;&#xff0c;打开该小程序&#xff0c;如下图所示 单击开始制作&#xff0c;然后选择二寸红底&#xff0c;如下图所示 然后单击相…

UI 自动化中的分层设计

以前的设计 在过去 UI 自动化测试领域有一个规范的设计模式是 page object 模式。 意思是测试用例不会直接定位页面元素&#xff0c; 而是把每一个页面封装成一个类。 在这个类中封装页面元素。 然后测试用例调用 page 类来操作页面元素完成测试用例。如下图&#xff1a; 以前…

前端面试题(二)答案版

面试形式&#xff1a;线上面试&#xff08;不露脸&#xff09;&#xff1a;时长40分钟 面试评价&#xff1a;由易到难&#xff0c;由细到全&#xff0c;比较不错 面试官&#xff1a;项目经理 面试官提问&#xff08;面试题&#xff09;&#xff1a; 1、聊聊最近写的这个项目…

【Meetup】探索Apache SeaTunnel的二次开发与实战案例

在数据科技快速演进的今天&#xff0c;业务场景的复杂化和数据量的激增&#xff0c;推动了大数据技术的迅速发展&#xff0c;在众多开源大数据处理工具中&#xff0c;Apache SeaTunnel以其强大的数据集成能力&#xff0c;成为众多企业的首选。 但随着应用深入&#xff0c;企业面…

32个小众搜索网站,相信一定有你想要的

首先问你一个问题&#xff0c;你平时都会用什么搜索引擎来进行搜索&#xff1f; 下面我将推荐32个小众搜索网站&#xff0c;相信一定会有你想要的。 1、多吉搜索 首先对因为拒绝商业化黑客攻击、不可抗力而停止服务的多吉搜索表示哀悼&#xff0c;这款由个人开发的小众搜索引…

Flutter基础 -- Flutter布局练习(小项目)

目录 1. Splash 布局&#xff08;第一页&#xff09; 1.1 目标 1.2 当前效果图 1.3 创建 Splash 界面 1.4 设置 MaterialApp 1.5 设置 Splash 背景色 1.6 布局 Splash 界面 1.7 总结 2. Splash 圆角图片 2.1 目标 2.2 当前效果图 2.3 蓝湖下载图片 2.4 图片导入项…

QT 信号和槽 一对多关联示例,一个信号,多个槽函数响应,一个信号源如何绑定多个槽函数

在窗体里放置一个单行文本编辑控件&#xff08;QLineEdit&#xff09;、一个标签控件&#xff08;QLabel&#xff09;和一个文本浏览控件&#xff08;QTextBrowser&#xff09;&#xff0c;在单行文 本编辑控件里的文本被编辑时&#xff0c;标签控件和文本浏览控件都会同步显示…

OpenMV学习笔记4——二维码识别

一、示例程序 按照下图顺序点击&#xff0c;即可打开官方在IDE中准备好的二维码实例程序&#xff1a; # QRCode Example # # This example shows the power of the OpenMV Cam to detect QR Codes # using lens correction (see the qrcodes_with_lens_corr.py script for hig…

【Vue】项目目录介绍和运行流程

文章目录 一、项目目录介绍二、public/index.html三、src/main.js四、运行流程 一、项目目录介绍 虽然脚手架中的文件有很多&#xff0c;目前咱们只需认识三个文件即可&#xff0c;这三个文件就决定了我们项目的运行 main.js 入口文件App.vue App根组件index.html 模板文件 我…

如何实现vue项目不同屏幕适配(2024最新)

文章目录 1.下载插件&#xff0c;修改px单位为rem单位2.配置vue.config.js(如下图位置所示)3.屏幕自适应4.项目实际使用 1.下载插件&#xff0c;修改px单位为rem单位 npm i postcss-plugin-px2rem2.配置vue.config.js(如下图位置所示) 注意在根目录下&#xff0c;如果没有该文…

【记录贴:分布式系列文章】

分布式系列文章目录 文章目录 分布式系列文章目录前言一、Redisq1.怎么判断是否命中缓存1. MySQL数据库如何检查询查缓存是否命中链接2.如何判断redis是否命中缓存链接 q2.Redis缓存穿透、雪崩、击穿以及分布式锁和本地锁 二、分布式q1.分布式订单号生成策略q2.接口幂等性,防止…

Crosslink-NX器件应用连载(9): USB3.0相机

作者&#xff1a;Hello&#xff0c;Panda 大家晚上好&#xff0c;很久没有分享设计案例了&#xff0c;实在是太忙了&#xff0c;精力十分有限&#xff0c;今天分享一个CrosslinkNX系列器件用作USB3.0相机的案例。其实就是分享一下使用CrosslinkNX器件设计USB3.0相机主要有两种…

腾讯云 TDMQ for Apache Pulsar 多地区高可用容灾实践

作者介绍 林宇强 腾讯云高级工程师 专注于消息队列、API网关、微服务、数据同步等 PaaS 领域。有多年的开发和维护经验&#xff0c;目前在腾讯云从事 TDMQ Pulsar 商业化产品方向的研发工作。 导语 本文将从四个维度&#xff0c;深入剖析 Pulsar 在多可用区高可用领域的容…

C++ 并发编程指南(5)线程状态及切换

文章目录 一、多线程状态及切换1、线程状态2、状态切换 前言&#xff1a; C中的线程状态及切换是操作系统和C线程库&#xff08;如POSIX线程或C11及之后的<thread>库&#xff09;共同管理的。线程的状态和切换是多线程编程中的重要概念&#xff0c;下面将简要介绍C线程的…

105.网络游戏逆向分析与漏洞攻防-装备系统数据分析-处理装备与技能数据的创建

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…

SpringBoot+Vue校园管理系统(前后端分离)

技术栈 JavaSpringBootMavenMyBatisMySQLVueElement-UIShiro 系统角色 管理员用户院系管理员 系统功能截图