鸿蒙应用开发——IM: Worker线程中模块未初始化异常处理

背景介绍

开发即时通讯IM SDK过程中遇到了用户反馈消息接收延迟,打开页面卡顿等问题,由于IM涉及到较多的网络请求和数据库操作等,而之前这些全部都放在了主线程,当涉及聊天内容与会话较多时,IO操作会导致应用卡顿丢帧。最开始设计时考虑到使用HarmonyOS 提供的Worker也TaskPool等多线程,但是由于时间关系和逻辑复杂,非共享内存方式的多线程有较大改造成本。现在生产环境遇到瓶颈,必须通过多线程机制解决了。

实现思路

消息接收方式采用推拉结合方式,当有新消息时通过长连接下发到客户端,客户端在通过http方式请求消息具体内容。在Android和iOS端采用了守护线程,一个专门拉取新消息的线程,当有事件时触发拉取消息接口调用,无事件时线程阻塞等待。

Worker与TaskPool选择

内存共享并发模型指多线程同时执行任务,这些线程依赖同一内存并且都有权限访问,线程访问内存前需要抢占并锁定内存的使用权,没有抢占到内存的线程需要等待其他线程释放使用权再执行。常见的并发模型有基于内存共享的模型和基于消息通信的模型。Actor并发模型是基于消息通信的并发模型的典型代表。它使开发者无需处理锁带来的复杂问题,并且具有较高的并发度,因此得到了广泛的应用。当前ArkTS提供了TaskPool和Worker两种并发能力,两者均基于Actor并发模型实现。

Actor并发模型每一个线程都是一个独立Actor,每个Actor有自己独立的内存,Actor之间通过消息传递机制触发对方Actor的行为,不同Actor之间不能直接访问对方的内存空间。

Actor并发模型相较于内存共享并发模型,不同线程间的内存是隔离的,因此不会出现线程竞争同一内存资源的情况。开发者无需处理内存上锁相关的问题,从而提高开发效率。

下面示意图展示了如何使用内存共享模型解决生产者消费者问题

image.png

下面示例简单展示了如何使用基于Actor模型的TaskPool并发能力来解决生产者消费者问题。

image.png

Actor并发模型中,线程不共享内存,需通过线程间通信机制传递任务和结果。非共享内存方式的线程类似于传统系统中进程的概念,独立内存,现成间交互有较高成本。

TaskPool和Worker的作用是为应用程序提供一个多线程的运行环境,用于处理耗时的计算任务或其他密集型任务。可以避免任务阻塞宿主线程,从而提高系统性能和资源利用率。

TaskPool和Worker的实现特点对比如下:

实现TaskPoolWorker
内存模型线程间隔离,内存不共享。线程间隔离,内存不共享。
参数传递机制采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。

支持ArrayBuffer转移和SharedArrayBuffer共享。
采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。

支持ArrayBuffer转移和SharedArrayBuffer共享。
参数传递直接传递,无需封装,默认进行transfer。消息对象唯一参数,需要自己封装。
方法调用直接将方法传入调用。在Worker线程中解析消息并调用对应方法。
返回值异步调用后默认返回。主动发送消息,需在onmessage中解析并赋值。
生命周期TaskPool自行管理生命周期,无需关心任务负载高低。开发者自行管理Worker的数量及生命周期。
任务池个数上限自动管理,无需配置。同个进程下,最多支持同时开启64个Worker线程,实际数量由进程内存决定。
任务执行时长上限3分钟(不包含Promise和async/await异步调用的耗时,例如网络下载、文件读写等I/O任务的耗时),长时任务无执行时长上限。无限制。
设置任务的优先级支持配置任务优先级。不支持。
执行任务的取消支持取消已经发起的任务。不支持。
线程复用支持。不支持。
任务延时执行支持。不支持。
设置任务依赖关系支持。不支持。
串行队列支持。不支持。
任务组支持。不支持。

由于TaskPool的工作线程会绑定系统的调度优先级,并支持负载均衡(自动扩缩容),相比之下,Worker需要开发者自行创建,存在创建耗时以及不支持设置调度优先级。因此,性能方面TaskPool优于Worker,官方推荐在大多数场景中使用TaskPool。

TaskPool偏向独立任务维度,该任务在线程中执行,无需关注线程的生命周期,超长任务(大于3分钟且非长时任务)会被系统自动回收。而Worker偏向线程的维度,支持长时间占据线程执行,需要开发者主动管理线程生命周期。

常见开发场景及适用说明如下:

  • 运行时间超过3分钟的任务(不包括Promise和async/await异步调用的耗时,如网络下载、文件读写等I/O任务的耗时):例如后台进行1小时的预测算法训练等CPU密集型任务,需要使用Worker。
  • 有关联的一系列同步任务:例如在一些需要创建、使用句柄的场景中,句柄每次创建都是不同的,该句柄需永久保存,保证使用该句柄进行操作,需要使用Worker。
  • 需要设置优先级的任务:例如图库直方图绘制场景,后台计算的直方图数据会用于前台界面的显示,影响用户体验,需要高优先级处理,需要使用TaskPool。
  • 需要频繁取消的任务:例如图库大图浏览场景,为提升体验,会同时缓存当前图片左右侧各2张图片,往一侧滑动跳到下一张图片时,要取消另一侧的一个缓存任务,需要使用TaskPool。
  • 大量或调度点分散的任务:例如大型应用的多个模块包含多个耗时任务,不方便使用Worker去做负载管理,推荐使用TaskPool。场景示例可参考批量数据写数据库场景。

我们这里需要实现一个类似守护线程的线程,所以采用worker方式自行管理线程生命周期等。

Worker实现守护线程

DevEco Studio支持一键生成Worker,在对应的{moduleName}目录下任意位置,单击鼠标右键 > New > Worker,即可自动生成Worker的模板文件及配置信息。这里我们创建MsgSyncWorker:

// worker.ets
import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';


const workerPort: ThreadWorkerGlobalScope = worker.workerPort;


// 注册onmessage回调,当Worker线程收到来自其宿主线程通过postMessage接口发送的消息时被调用,在Worker线程执行
workerPort.onmessage = (e: MessageEvents) => {
  let data: string = e.data;
  console.info('workerPort onmessage is: ', data);


  // 向主线程发送消息
  workerPort.postMessage('2');
}


// 注册onmessageerror回调,当Worker对象接收到一条无法被序列化的消息时被调用,在Worker线程执行
workerPort.onmessageerror = () => {
  console.info('workerPort onmessageerror');
}


// 注册onerror回调,当Worker在执行过程中发生异常被调用,在Worker线程执行
workerPort.onerror = (err: ErrorEvent) => {
  console.info('workerPort onerror err is: ', err.message);
}

IDE创建的Worker默认实现了回调函数,主要需要处理onmesssage的注册,用来接收宿主线程的指令,onmessage对应函数会执行在子线程,可以通过workerPort.postMessage给宿主线程发送消息。

接着在宿主线程中启动worker:

// 创建Worker对象
let workerInstance = new worker.ThreadWorker('entry/ets/workers/worker.ets');


// 注册onmessage回调,当宿主线程接收到来自其创建的Worker通过workerPort.postMessage接口发送的消息时被调用,在宿主线程执行
workerInstance.onmessage = (e: MessageEvents) => {
let data: string = e.data;
console.info("workerInstance onmessage is: ", data);
}

// 注册onerror回调,当Worker在执行过程中发生异常时被调用,在宿主线程执行
workerInstance.onerror = (err: ErrorEvent) => {
console.info("workerInstance onerror message is: " + err.message);
}


// 注册onmessageerror回调,当Worker对象接收到无法序列化的消息时被调用,在宿主线程执行
workerInstance.onmessageerror = () => {
console.info('workerInstance onmessageerror');
}


// 注册onexit回调,当Worker销毁时被调用,在宿主线程执行
workerInstance.onexit = (e: number) => {
// 如果Worker正常退出,code为0;如果异常退出,code为1
console.info("workerInstance onexit code is: ", e);
}


// 发送消息给Worker线程
workerInstance.postMessage('1');
遇到问题

因为HarmonyOS中线程间内存不共享,所以之前主线程初始化的一些数据库和网络单例类需要重新再初始化,但是在初始化数据库是遇到了崩溃:Error message: UserDaoHelper is not initialized。

UserDaoHelper是一个单例,在执行UserDaoHelper的getInstance之前就崩溃了,UserDaoHelper导入失败了。

问题原因分析

出现模块未初始化错误是因为模块间循环依赖引起。模块间循环依赖可能导致应用运行时模块依赖的变量未初始化,如下示例。index.ets文件执行前,会先执行依赖的page.ets文件,page.ets文件执行时又循环依赖了index.ets导出的foo符号。此时index.ets文件未执行,foo变量尚未完成初始化,会导致运行时异常。

// index.ets
import { bar } from './page'
export function foo() {
  bar()
}


// page.ets
import { foo } from './index'
export function bar() {
  foo()
}
bar()
解决方案

可以通过DevEco Studio中Code Linter检查工具识别应用代码中的循环依赖并进行代码重构,消除循环依赖影响。

首先在工程根目录下创建code-linter.json5配置文件,配置如下:

{
  "files": [ // 用于表示配置适用的文件范围的 glob 模式数组。
  "**/*.js",
  "**/*.ts",
  "**/*.ets"
  ],
  "rules": {
  "@security/no-cycle": "error" // 配置循环依赖检查规则。
  }
}

接着在工程管理窗口中鼠标选中工程根目录,右键选择Code Linter > Full Linter执行代码全量检查。
 

image.png


最后根据检查结果,对应用代码中的循环依赖部分进行代码重构。

鸿蒙核心技术##鸿蒙开发工具##DevEco Studio

<think>好的,用户想了解在鸿蒙系统上实现IM即时通讯的方法和开发教程。首先,我需要整理已有的引用资料,看看有哪些相关信息可以提取。 根据引用4,融云已经开发鸿蒙NEXT版的IM SDK,用户可以直接使用他们的SDK进行集成。这应该是一个比较直接的解决方案。步骤包括初始化IM引擎、连接服务器、设置监听器等,这些在引用4中有代码示例,可以整理出来。 然后,引用3提到了MobileIMSDK的开源框架,用户也可以选择这个方案。需要说明如何集成SDK并进行基础功能的开发,比如消息发送和接收。这部分可能需要用户自己处理更多底层逻辑,适合需要定制的场景。 引用5提到鸿蒙的一多能力,可以在UI布局上实现一次开发多端部署,这对IM应用来说很重要。需要解释如何利用自适应布局和组件复用,确保不同设备的兼容性。 另外,引用1和引用2提供了学习资料和SDK市场的信息,应该作为资源推荐给用户,帮助他们获取更多支持。 接下来要确保回答结构清晰,分步骤讲解两种实现方式,并提到优化和注意事项。同时,生成的相关问题需要覆盖性能优化、安全机制、消息存储、协议选择以及开源框架比较等方面,帮助用户深入理解。 需要检查是否所有LaTeX格式都正确,行内公式用$...$,独立公式用$$...$$,但用户的问题中没有涉及数学公式,所以可能不需要使用。但根据系统指令,如果有任何数学表达式,必须正确应用格式。不过当前问题主要涉及开发步骤,可能不需要数学公式,但需要保持中文回答。 最后,确保引用标识正确添加到对应的内容末尾,例如融云的SDK引用[^2][^4],MobileIMSDK引用[^3],一多能力引用[^5],学习资料引用[^1]。 总结回答结构: 1. 两种实现方式:第三方SDK(融云)和开源框架(MobileIMSDK) 2. 开发步骤分点说明,附带代码示例 3. 注意事项与优化建议 4. 相关资源推荐 5. 相关问题生成 需要确保语言简洁,步骤明确,引用正确,并且符合用户要求的系统级指令。</think>### 鸿蒙系统 IM即时通讯 实现方法与开发教程 #### 一、实现方式与核心步骤 ##### 1. 使用第三方 SDK(以融云为例) - **SDK 初始化** 在 UIAbility 的 `onCreate()` 方法中初始化 IM 引擎,需传入融云提供的 App Key: ```typescript // 在 UIAbility 中获取 context let context = this.context; let initOption = new InitOption(); let appKey = "从融云后台获取的 appKey"; IMEngine.getInstance().init(context, appKey, initOption); ``` 初始化后可通过监听器处理连接状态和消息回调。 - **用户连接与消息收发** 使用 Token 连接服务器,并实现消息发送接口: ```typescript // 连接服务器 let token = "用户身份凭证"; IMEngine.getInstance().connect(token); // 发送文本消息 let message = new TextMessage("接收方ID", "Hello HarmonyOS!"); IMEngine.getInstance().sendMessage(message); ``` ##### 2. 基于开源框架(如 MobileIMSDK) - **SDK 集成** 从 MobileIMSDK 鸿蒙端仓库获取 SDK,配置依赖后初始化核心模块: ```typescript // 初始化 IM 客户端 let client = new IMClient(); client.init("服务器地址", 端口); ``` - **基础功能开发** 实现消息监听与发送逻辑: ```typescript // 消息监听 client.setOnMessageListener((message) => { console.log("收到消息: " + message.content); }); // 发送消息 client.sendMessage("接收方ID", "测试消息"); ``` 需自行处理消息存储、重试机制和协议解析。 #### 二、注意事项与优化建议 1. **网络兼容性** 鸿蒙的多端协同特性要求适配不同网络环境(如 Wi-Fi 与蜂窝网络切换),需实现自动重连和心跳检测机制。 2. **一多能力应用** 利用鸿蒙的 `自适应布局` 和 `响应式组件`,实现一次开发多端部署: ```typescript // 示例:自适应消息列表布局 @Builder MessageItem(message: Message) { Flex({ direction: FlexDirection.Row }) { Text(message.content) .fontSize(16) .layoutWeight(1) } .padding(10) .width('100%') } ``` 此布局可自动适配手机、平板等不同屏幕尺寸[^5]。 3. **性能优化** 使用鸿蒙的 `Worker` 线程处理消息编解码和数据库操作,避免阻塞主线程。 #### 三、相关资源推荐 - 鸿蒙官方开发文档: - 融云鸿蒙 SDK 市场: - MobileIMSDK 鸿蒙开发手册:
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值