初学者的鸿蒙多线程并发之 TaskPool 踩坑之旅

news2024/11/16 3:26:04

1. 背景

  • 目标群体:鸿蒙初学者

  • 版本:HarmonyOS 3.1/4.0

  • 背景:鸿蒙 App 的全局路由管理功能,需要在 App 启动时初始化对 raw 下的相关配置文件进行读取、解析并缓存。App 启动时涉及到了大量模块的初始化,好多模块都涉及到 IO 以及计算操作。鸿蒙的 ArkTS 是在继承 TypeScript 语法的基础上进行了优化,但是其脱离不了 js,js 又是单线程的,故担忧其性能。果断查阅官方文档描述,不出所料官方是这么回复的:

    ArkTS 层接口的异步如果不涉及 I/O 操作,则异步任务会在主线程的微任务执行时机触发,仍然占用主线程。推荐使用 TaskPool,分发到后台任务池进行。

就是这个回复,让我这个初学者开启了多线程异步任务的踩坑之旅。

2. 并发

2.1 概述

并发是指在同一时间段内,能够处理多个任务的能力。HarmonyOS 系统提供了异步并发和多线程并发两种处理策略。

  • 异步并发是指异步代码在执行到一定程度后会被暂停,以便在未来某个时间点继续执行,这种情况下,同一时间只有一段代码在执行。

  • 多线程并发允许在同一时间段内同时执行多段代码。在主线程继续响应用户操作和更新 UI 的同时,后台也能执行耗时操作,从而避免应用出现卡顿。

ArkTS 支持异步并发和多线程并发。

  • Promise 和 async/await 提供异步并发能力,适用于单次 I/O 任务的开发场景。详细请参见异步并发概述。(这个就是 js 的异步任务,官方文档资料中也指出其适用于单次 I/O 的场景开发,例如一次网络请求、一次文件读写等操作。)

  • TaskPool 和 Worker 提供多线程并发能力,适用于 CPU 密集型任务、I/O 密集型任务和同步任务等并发场景。详细请参见多线程并发概述。(我们需要的就是这个,App 启动过程中涉及大量 IO、计算等操作)。当任务不需要长时间(3 分钟)占据后台线程,而是一个个独立的任务时,推荐使用 TaskPool。

2.2 多线程并发之 TaskPool

并发模型是用来实现不同应用场景中并发任务的编程模型,常见的并发模型分为基于内存共享的并发模型和基于消息通信的并发模型。

Actor 并发模型作为基于消息通信并发模型的典型代表,不需要开发者去面对锁带来的一系列复杂偶发的问题,同时并发度也相对较高,因此得到了广泛的支持和使用。

当前 ArkTS 提供了 TaskPool 和 Worker 两种并发能力,TaskPool 和 Worker 都基于 Actor 并发模型实现。

PS:TaskPool 会随着应用进程起一个线程,省去了首次任务执行创建线程的开销,线程创建开销较小。

鸿蒙的多线程并发都是基于 Actor 并发模型实现,不是基于内存共享的。你不需要考虑对内存上锁导致的一系列功能、性能问题。但是 Actor 并发模型每一个线程都是一个独立 Actor,每个 Actor 有自己独立的内存,Actor 之间通过消息传递机制触发对方 Actor 的行为,不同 Actor 之间不能直接访问对方的内存空间。Actor 并发模型线程之间是内存隔离的。

3.TaskPool 开发流程(踩坑之旅)

好的,看完文档我就开始按照官方流程进行了如下代码编写:

1. 使用@Concurrent 注解定义并发函数,在函数中执行 IO、计算等耗时操作。

//并发函数
@Concurrent
async function loadDnsTable(): Promise<Map<string, string>> {
  //TODO:读取raw下的资源文件,对齐进行解析并且缓存
}

2. Harmony 要求并发函数必须是全局 function 不能是类方法,???那我如何调用我自己创建的 RawTableReader 类的方法去读取、解析并且缓存路由表。故再次翻阅官方文档,终于在 FAQ 中找到了答案:如何将类 Java 语言的线程模型(内存共享)的实现方式转换成在 ArkTS 的线程模型下(内存隔离)的实现方式话说你们就不能将他写在 taskpool 文档里么?!

export interface RawTableReader extends lang.ISendable {
  readRawTable(context: common.Context): Map<string, string>;
}

并发函数修改后如下:

@Concurrent
function loadDnsTable(args: Object[]): Map<string, string> {
  let rawTableReader: RawTableReader = args[0] as RawTableReader;
  //此处的context类是EntryAbility启动时后注入到VirtualDomain单例类中的
  let context: common.Context = VirtualDomain.getInstance().getAppContext();
  return rawTableReader.readRawTable(context);
}

3. 使用 TaskPool 执行包含密集 I/O 的并发函数:通过调用 execute()方法执行任务,并在回调中进行调度结果处理。

let task: taskpool.Task = new taskpool.Task('vdn', loadDnsTable, rawTableReader);
taskpool.execute(task).then((result: Object) => {
    let r: Map<string, string> = result as Map<string, string>;
  }).catch((error: BusinessError) => {
  VdnLog.warn(`loadDnsTable error code = ${error.code} message = ${error.message}`);
});

4. 好了开发完了,我开始了我的一次运行。不出意外报错了,断点调试半天大致意思是:context is undefined

  1. 难道单例类没初始化注入 context?检查代码以及断点再次尝试,EntryAbility 启动时已经注入了全局 context。

  2. 好吧,那我直接在 context 获取地方进行断点。又是小半天过去,我发现了问题两次调用 VirtualDomain.getInstance()返回的实例竟然不是一个?! ! .

  3. 我又思考并且到处翻阅文档好久,总算想起来了 Harmony 的多线程是基于 Actor 的内存隔离的不是内存共享的,我在主线程注入的 context 的 VirtualDomain 单例对象跟我子线程获取到的根本就不是一个,那肯定就 undefined 了。

5. 我想起来之前华为的官方人员在 FAQ 中回复可以使用应用全局状态存储 AppStorage 缓存 context 对象,于是我继续修改代码 context 改为使用官方全局单例 AppStorage 进行存储获取。结果是:再次失败,好了我用实践证明了官方的 AppStorage 在多线程情况下也是有问题的。大家使用时一定注意!

6. 我就不信一个 context 我就解决不了了?再次查阅官方文档皇天不负苦心人,我再次找到了 TaskPool 和 Worker 支持的序列化类型这篇文档里描述了 context 是 Native 绑定对象可以在 TaskPool 中进行序列化传输。因此再次修改代码

export calss xxx {
    ...
    let context: common.Context = VirtualDomain.getInstance().getAppContext();
    let task: taskpool.Task = new taskpool.Task('vdn', loadDnsTable, rawTableReader, context);
    ...
}
@Concurrent
function loadDnsTable(args: Object[]): Map<string, string> {
  let rawTableReader: RawTableReader = args[0] as RawTableReader;
  let context: common.Context = args[1] as common.Context;
  return rawTableReader.readRawTable(context);
}

7.这次代码直接报错了 Casting "Non-sendable" data to "Sendable" type is not allowed (arkts-sendable-as-expr) <ArkTSCheck>

我按照你的官方 task api 构建的 task,你也说了 context 是 Native 绑定对象是支持的序列化类型。结果 let context: common.Context = args[1] as common.Context; 你直接给我编译报错?

8. 继续思考,进行了如下修改,编译 OK,运行测试也👌🏻,我的踩坑之旅总算结束了😭。

//虽然入参是Object[]对象,这里的args要注意必须使用lang.ISendable[],否则就会编译报错
@Concurrent
function loadDnsTable(args: lang.ISendable[]): Map<string, string> {
  let rawTableReader: RawTableReader = args[0] as RawTableReader;
  let context: common.Context = args[1] as common.Context;
  return rawTableReader.readRawTable(context);
}

4. 总结

4.1 技术经验

  • Harmony 单次 IO 可以使用异步任务,如果涉及到多次 IO 或者大量计算建议使用多线程异步并发,异步任务的微任务也会有一定程度卡顿。

  • Harmony 的多线程是基于 Actor 内存隔离的,单例是失效的,如果需要使用相关成员变量或者方法请进行序列化传输

  • Harmony 官方的全局状态存储 AppStorage 在多线程情况下也是失效的

  • Harmony 的 TaskPool 会随着应用进程起一个线程,省去了首次任务执行创建线程的开销,线程创建开销较小

  • 如果是时长大于 3 分钟的耗时任务,需要使用 Worker

4.2 后续

  • taskpool 还支持组任务、取消任务、依赖任务。

  • worker 的用法

  • 未来 Harmony 正式版推出之后是否会推出 App 进程下线程可以共享数据的模块

5. 团队介绍

三翼鸟数字化技术平台-智家APP平台」通过持续迭代演进移动端一站式接入平台为三翼鸟APP、智家APP等多个APP提供基础运行框架、系统通用能力API、日志、网络访问、页面路由、动态化框架、UI组件库等移动端开发通用基础设施;通过Z·ONE平台为三翼鸟子领域提供项目管理和技术实践支撑能力,完成从代码托管、CI/CD系统、业务发布、线上实时监控等Devops与工程效能基础设施搭建。

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

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

相关文章

【machine learning-15-如何判定梯度下降是否在收敛】

我们在运行梯度下降的时候&#xff0c;如何判定梯度下降是否在收敛呢&#xff1f; 梯度下降的时候&#xff0c;权重和偏置根据如下的公式同时更新&#xff1a; 程序要做的就是更新w 和 b&#xff0c;让梯度下降尽快的收敛&#xff0c;但是如何判定正在收敛呢&#xff1f; 方法…

关于神经网络的一个介绍

这篇文章中&#xff0c;我将简单介绍下与神经网络有关的东西&#xff0c;包括它的基本模型&#xff0c;典型的算法以及与深度学习的联系等内容。 一、神经元 神经网络是由许多个神经元组成的&#xff0c;在生物的神经网络中&#xff0c;就是神经元间相互连接&#xff0c;传递…

Arthas getstatic(查看类的静态属性 )

文章目录 二、命令列表2.1 jvm相关命令### 2.1.7 getstatic&#xff08;查看类的静态属性 &#xff09; 二、命令列表 2.1 jvm相关命令 ### 2.1.7 getstatic&#xff08;查看类的静态属性 &#xff09; 使用场景&#xff1a; 我们项目部署在linux上&#xff0c;我有个本地内存…

从一到无穷大 #35 Velox Parquet Reader 能力边界

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 引言源码分析功能描述功能展望 引言 InfluxDB IOX这样完全不使用索引&#xff0c;只…

《沧浪之水》读后感

未完待续..... 未完待续.... 未完待续.... 【经典语录】 01、我一辈子的经验就是不要做瞎子&#xff0c;也不能做聋子&#xff0c;该听到的信息要听到&#xff0c;但是要做哑巴&#xff0c;看到了听到了心中有数就行了&#xff0c;可千万不要张口说什么。 02、你刚从学校毕业…

MQ入门(一):同步调用和异步调用--RabbitMQ基础入门

目录 1.初识MQ 1.1.同步调用 1.2.异步调用 1.3.技术选型 2.RabbitMQ 2.1.安装部署 2.2.RabbitMQ基本架构 2.3.收发消息 2.3.1.交换机 2.3.2.队列 2.3.3.绑定关系 2.3.4.发送消息 2.4.数据隔离 2.4.1.用户管理 2.4.2.virtual host 1.初识MQ 微服务一旦拆分&…

web前端字段大小写下划线转换工具

文章目录 前言一、如何使用&#xff1f;二、相关代码总结 前言 程序员在敲代码的过程中都要命名一些字段&#xff0c;但是Java语言对字段的命名规范和sql命名规范不一样&#xff0c;如下图所示&#xff0c;这种机械性的转换工作很劳神费力&#xff0c;为了省点劲写了一个web小…

尚品汇-Jenkins部署构建服务模块、Linux快照备份(五十七)

目录&#xff1a; &#xff08;1&#xff09;构建作业&#xff08;server-gateway&#xff09; &#xff08;2&#xff09;构建service_product模块 &#xff08;3&#xff09;演示添加新代码 &#xff08;4&#xff09;学会使用linux快照 &#xff08;1&#xff09;构建作…

在SpringCloud中实现服务间链路追踪

在微服务架构中&#xff0c;由于系统的复杂性和多样性&#xff0c;往往会涉及到多个服务之间的调用。当一个请求经过多个服务时&#xff0c;如果出现问题&#xff0c;我们希望能够快速定位问题所在。这就需要引入链路追踪机制&#xff0c;帮助我们定位问题。 Spring Cloud为我们…

【沪圈游戏公司作品井喷,游戏产业复兴近在眼前】

近期财报季中&#xff0c;腾讯、网易及B站等国内游戏巨头纷纷亮出亮眼的游戏业务表现&#xff0c;均实现了接近或超越双位数的同比增长。然而&#xff0c;审视过去一年&#xff0c;国内游戏行业仍笼罩在宏观经济“降本增效”的阴影下。 行业数据揭示&#xff0c;全国游戏公司社…

封装 wx.request 的必要性及其实现方式

目录 为什么需要封装 wx.request 1. 避免回调地狱 2. 统一管理 3. 扩展功能 小程序异步 API 的改进 封装实现方式 在小程序开发中&#xff0c;网络请求是不可或缺的功能之一。小程序提供了 wx.request API 来实现网络请求&#xff0c;但直接使用这个 API 在复杂场景下可…

关于SpringBoot项目使用maven打包由于Test引起的无法正常打包问题解决

一、问题描述 在日常工作中&#xff0c;在接手项目时&#xff0c;项目未必是“正常”的&#xff0c;一般平常搭建项目&#xff0c;都不会采用一键式生成的方式&#xff0c;现在说下旧项目&#xff0c;可能项目结构并不是那么简洁&#xff0c;通常都带有与main同层级的test&…

Cpp类和对象(中续)(5)

文章目录 前言一、赋值运算符重载运算符重载赋值运算符重载赋值运算符不可重载为全局函数前置和后置的重载 二、const修饰成员函数三、取地址及const取地址操作符重载四、日期类的实现构造函数日期 天数日期 天数日期 - 天数日期 - 天数日期类的大小比较日期类 > 日期类日…

嵌入式系统stm32cube本地安装出现的问题

stm32cube在线安装很慢&#xff0c;本地安装中出现的一个bug stm32cube_fw_f4_v1281安装成功之后&#xff0c;如果想安装stm32cube_fw_f4_v1281会提示stm32cube_fw_f4_v1280未安装。 如果先安装stm32cube_fw_f4_v1280之后&#xff0c;再安装stm32cube_fw_f4_v1281还会提示这个…

Python模拟鼠标轨迹[Python]

一.鼠标轨迹模拟简介 传统的鼠标轨迹模拟依赖于简单的数学模型&#xff0c;如直线或曲线路径。然而&#xff0c;这种方法难以捕捉到人类操作的复杂性和多样性。AI大模型的出现&#xff0c;能够通过深度学习技术&#xff0c;学习并模拟更自然的鼠标移动行为。 二.鼠标轨迹算法实…

C#如何把写好的类编译成dll文件

1 新建一个类库项目 2 直接改写这个Class1.cs文件 3 记得要添加Windows.Forms引用 4 我直接把在别的项目中做好的cs文件搞到这里来&#xff0c;连文件名也改了&#xff08;FilesDirectory.cs&#xff09;&#xff0c;这里using System.Windows.Forms不会报错&#xff0c;因为前…

go项目多环境配置

1.java项目配置加载最佳实践 在 Spring Boot 项目中&#xff0c;配置文件的加载和管理是开发过程中不可或缺的一部分。Spring Boot 提供了一套灵活且强大的机制来加载配置文件&#xff0c;使得开发者能够根据不同的环境和需求轻松地管理配置。当多个位置存在相同的配置文件时&…

Python语法进阶之路

一、Python基础 1.1 注释 定义和作用 对代码解释说明&#xff0c;增强可读性 单行注释 # 多行注释 """ 这是一个多行注释 """ 1.2 变量及变量类型 定义和作用 计算机目的是计算&#xff0c;编程是为了更方便计算&#xff0c;计算对象就是…

vue循环渲染动态展示内容案例(“更多”按钮功能)

当我们在网页浏览时&#xff0c;常常会有以下情况&#xff1a;要展示的内容太多&#xff0c;但展示空间有限&#xff0c;比如我们要在页面的一部分空间中展示较多的内容放不下&#xff0c;通常会有两种解决方式&#xff1a;分页&#xff0c;“更多”按钮。 今天我们的案例用于…

mybatis 配置文件完成增删改查(二):根据条件查询一个

文章目录 参数占位符#{}:会将其替换为&#xff1f; ——为了防止sql注入${}:会将其替换为实际接收到的数据&#xff0c;拼sql ——无法防止sql注入 查询一个sql特殊字符的处理 参数占位符 #{}:会将其替换为&#xff1f; ——为了防止sql注入 ${}:会将其替换为实际接收到的数据…