HarmonyOS开发实战( Beta5版)线程间通信场景最佳实践

news2024/11/24 13:55:12

简介

在应用开发中,经常会需要处理一些耗时的任务,如果全部放在主线程中执行就会导致阻塞,从而引起卡顿或者掉帧现象,降低用户体验,此时就可以将这些耗时操作放到子线程中处理。通常情况下,子线程可以独立完成自己的任务,但是很多时候需要将数据从主线程传递到子线程,或者将子线程的执行结果返回给主线程。本篇文章将通过以下几种场景和示例,呈现如何在OpenHarmony应用开发中实现主线程和子线程的数据通信。

独立的耗时任务

如果耗时任务是可以独立执行的,只需要在任务执行完毕后将结果返回给主线程,可以通过以下方式实现。

首先,引入TaskPool模块。

import { taskpool } from '@kit.ArkTS';

然后,实现子线程需要执行的任务。

@Concurrent // 在Task中执行的方法,需要添加@Concurrent注解,否则无法正常调用。
export function loadPicture(count: number): IconItemSource[] {
  let iconItemSourceList: IconItemSource[] = [];
  // 遍历添加6*count个IconItem的数据
  for (let index = 0; index < count; index++) {
    const numStart: number = index * 6;
    // 此处循环使用6张图片资源
    iconItemSourceList.push(new IconItemSource($r('app.media.nearby'), `item${numStart + 1}`));
    iconItemSourceList.push(new IconItemSource($r('app.media.scan'), `item${numStart + 2}`));
    iconItemSourceList.push(new IconItemSource($r('app.media.shop'), `item${numStart + 3}`));
    iconItemSourceList.push(new IconItemSource($r('app.media.cards'), `item${numStart + 4}`));
    iconItemSourceList.push(new IconItemSource($r('app.media.album'), `item${numStart + 5}`));
    iconItemSourceList.push(new IconItemSource($r('app.media.applet'), `item${numStart + 6}`));
  }
  return iconItemSourceList;
}

最后,通过TaskPool中的execute方法执行Task。

// ...
// 创建Task
let lodePictureTask: taskpool.Task = new taskpool.Task(loadPicture, 30);
// 执行Task,并返回结果
taskpool.execute(lodePictureTask).then((res: IconItemSource[]) => {
  // loadPicture方法的执行结果
  this.iconItemSourceList = res; 
})
// ...

多个任务同时执行

如果有多个任务同时执行,由于任务的复杂度不同,执行时间会不一样,返回数据的时间也是不可控的。如果主线程需要所有任务执行完毕的数据,可以通过下面这种方式实现。

// ...
let taskGroup: taskpool.TaskGroup = new taskpool.TaskGroup();
taskGroup.addTask(new taskpool.Task(loadPicture, 30));
taskGroup.addTask(new taskpool.Task(loadPicture, 20));
taskGroup.addTask(new taskpool.Task(loadPicture, 10));
taskpool.execute(taskGroup).then((ret: IconItemSource[][]) => {
  for (let i = 0; i < ret.length; i++) {
    for (let j = 0; j < ret[i].length; j++) {
      this.iconItemSourceList.push(ret[i][j]);
    }
  }
})
// ...

在该场景中,将需要执行的Task放到了一个TaskGroup里面,当TaskGroup中所有的Task都执行完毕后,会把每个Task运行的结果都放在一个数组中返回到主线程,而不是每执行完一个Task就返回一次,这样就可以在返回的数据里拿到所有的Task执行结果,方便主线程使用。

除此以外,如果Task需要处理的数据量较大(比如一个列表中有10000条数据),把这些数据都放在一个Task中处理也是比较耗时的。那么就可以将原始数据拆分成多个列表,并将每个子列表分配给一个独立的Task进行执行,并且等待全部执行完毕后拼成完整的数据,这样可以节省处理时间,提升用户体验。

Task任务与主线程通信

如果一个Task,不仅需要返回最后的执行结果,而且需要定时通知主线程状态、数据的变化,或者需要分段返回数量级较大的数据(比如从数据库中读取大量数据),可以通过下面这种方式实现。

首先,实现一个方法,用来接收Task发送的消息。

function notice(data: number): void {
  console.info("子线程任务已执行完,共加载图片: ", data);
}

然后,在需要执行的Task任务中,添加sendData()接口将消息发送给主线程。

// 通过Task的sendData方法,即时通知主线程信息
@Concurrent
export function loadPictureSendData(count: number): IconItemSource[] {
  let iconItemSourceList: IconItemSource[] = [];
  // 遍历添加6*count个IconItem的数据
  for (let index = 0; index < count; index++) {
    const numStart: number = index * 6;
    // 此处循环使用6张图片资源
    iconItemSourceList.push(new IconItemSource($r('app.media.nearby'), `item${numStart + 1}`));
    iconItemSourceList.push(new IconItemSource($r('app.media.scan'), `item${numStart + 2}`));
    iconItemSourceList.push(new IconItemSource($r('app.media.shop'), `item${numStart + 3}`));
    iconItemSourceList.push(new IconItemSource($r('app.media.cards'), `item${numStart + 4}`));
    iconItemSourceList.push(new IconItemSource($r('app.media.album'), `item${numStart + 5}`));
    iconItemSourceList.push(new IconItemSource($r('app.media.applet'), `item${numStart + 6}`));
    taskpool.Task.sendData(iconItemSourceList.length);
  }
  return iconItemSourceList;
}

最后,在主线程通过onReceiveData()接口接收消息。

// ...
let lodePictureTask: taskpool.Task = new taskpool.Task(loadPictureSendData, 30);
// 设置notice方法接收Task发送的消息
lodePictureTask.onReceiveData(notice);
taskpool.execute(lodePictureTask).then((res: IconItemSource[]) => {
  this.iconItemSourceList = res;
})
// ...

这样主线程就可以通过notice()接口接收到Task发送的数据。

Worker和主线程的即时消息通信

在ArkTS中,Worker相对于TaskPool存在一定的差异性,有数量限制但是可以长时间存在。一个Worker中可能会执行多个不同的任务,每个任务执行的时长或者返回的结果可能都不相同,主线程需要根据情况调用Worker中的不同方法,Worker则需要及时地将结果返回给主线程,此时可以通过下面的方法实现。

首先,创建一个Worker,可以根据参数执行不同的任务。

import { worker, MessageEvents, ThreadWorkerGlobalScope } from '@kit.ArkTS';
 
const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
// Worker接收主线程的消息,根据数据类型调用对应的方法
workerPort.onmessage = (e: MessageEvents): void => {
  if (typeof e.data === "string") {
    try {
      // 调用方法无入参
      let res: string = workerPort.callGlobalCallObjectMethod("picData", "setUp", 0) as string;
      console.info("worker: ", res);
    } catch (error) {
      // 异常处理
      console.error("worker: error code is " + error.code + " error message is " + error.message);
    }
  } else if (e.data instanceof Array) {
    // 将数据的前4条返回回去
    workerPort.postMessage(e.data.slice(0, 4));
  }
}

然后在主线程中创建这个Worker的对象,在点击Button的时候调用postMessage向Worker发送消息,通过Worker的onmessage方法接收Worker返回的数据。

import { worker, MessageEvents } from '@kit.ArkTS';
// ...
@State iconItemSourceList: IconItemSource[] = [];
// 创建Worker对象
workerInstance: worker.ThreadWorker = new worker.ThreadWorker("entry/ets/pages/workers/Worker.ts");
aboutToAppear() {
  // 初始化Worker
  this.initWorker();
  for (let index = 0; index < 20; index++) {
    const numStart: number = index * 6;
    // 此处循环使用6张图片资源
    this.iconItemSourceList.push(new IconItemSource($r('app.media.nearby'), `item${numStart + 1}`));
    this.iconItemSourceList.push(new IconItemSource($r('app.media.scan'), `item${numStart + 2}`));
    this.iconItemSourceList.push(new IconItemSource($r('app.media.shop'), `item${numStart + 3}`));
    this.iconItemSourceList.push(new IconItemSource($r('app.media.cards'), `item${numStart + 4}`));
    this.iconItemSourceList.push(new IconItemSource($r('app.media.album'), `item${numStart + 5}`));
    this.iconItemSourceList.push(new IconItemSource($r('app.media.applet'), `item${numStart + 6}`));
  }
}
initWorker(){
  // 通过onmessage方法接收Worker返回的数据
  this.workerInstance.onmessage = (e: MessageEvents): void => {
    if (e.data instanceof Array) {
      this.iconItemSourceList = e.data;
    }
 }
}
// ...
Button('将图片变成5个', { type: ButtonType.Normal, stateEffect: true })
  .fontSize(14)
  .borderRadius(8)
  .backgroundColor('# 317aff')
  .width(250)
  .height(60)
  .margin({
    top: 30
  })
  .onClick(() => {
    // 将数据传到Worker中
    this.workerInstance.postMessage(this.iconItemSourceList);
  })
// ...

在这段示例代码中,Worker做了2种不同的处理:当传入的数据是个string类型时,调用callGlobalCallObjectMethod同步调用主线程中的接口;当传入Array类型的时候,将Array的前4条数据返回给主线程。这样就可以实现主线程和Worker间的即时通信,方便主线程使用Worker的运行结果。

Worker子线程同步调用主线程的接口

如果一个接口在主线程中已经实现了,Worker需要调用该接口,那么可以使用下面这种方式实现。

首先,在主线程实现需要调用的接口,并且创建Worker对象,在Worker上注册需要调用的接口。

import { worker } from '@kit.ArkTS';
// 创建Worker对象
const workerInstance: worker.ThreadWorker = new worker.ThreadWorker("entry/ets/pages/workers/Worker.ts");
 
class PicData {
  public iconItemSourceList: IconItemSource[] = [];
  
  public setUp(): string {
    for (let index = 0; index < 20; index++) {
      const numStart: number = index * 6;
      // 此处循环使用6张图片资源
      this.iconItemSourceList.push(new IconItemSource($r('app.media.nearby'), `item${numStart + 1}`));
      this.iconItemSourceList.push(new IconItemSource($r('app.media.scan'), `item${numStart + 2}`));
      this.iconItemSourceList.push(new IconItemSource($r('app.media.shop'), `item${numStart + 3}`));
      this.iconItemSourceList.push(new IconItemSource($r('app.media.cards'), `item${numStart + 4}`));
      this.iconItemSourceList.push(new IconItemSource($r('app.media.album'), `item${numStart + 5}`));
      this.iconItemSourceList.push(new IconItemSource($r('app.media.applet'), `item${numStart + 6}`));
   }
   return "setUpIconItemSourceList success!";
  }
}
 
let picData = new PicData();
// 在Worker上注册需要调用的对象
workerInstance.registerGlobalCallObject("picData", picData);
workerInstance.postMessage("run setUp in picData");

然后,在Worker中通过callGlobalCallObjectMethod接口就可以调用主线程中的setUp()方法了。

// ...
try {
  // 调用方法无入参
  let res: string = workerPort.callGlobalCallObjectMethod("picData", "setUp", 0) as string;
  console.info("worker: ", res);
} catch (error) {
  // 异常处理
  console.error("worker: error code is " + error.code + " error message is " + error.message);
}
// ...

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

希望这一份鸿蒙学习文档能够给大家带来帮助~


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频教程+学习PDF文档

(鸿蒙语法ArkTS、TypeScript、ArkUI教程……)

 纯血版鸿蒙全套学习文档(面试、文档、全套视频等)

                   

鸿蒙APP开发必备

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

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

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

相关文章

bcftools报错|The sequence “chr1“ not defined in the header: chr1.recode.vcf

1、报错信息 The sequence "chr1" not defined in the header: chr1.recode.vcf (Quick workaround: index the file.) 所使用的命令&#xff0c;目的是想合并所提取的特定染色体。 bcftools concat -O v / -o varscan.indel_merged.vcf chr1.recode.vcf chr2.reco…

超好用的图纸加密软件排行榜 | 2024图纸加密软件的七款最优选择!

数字化设计日益普及的今天&#xff0c;图纸作为设计与工程的核心载体&#xff0c;其安全性成为了企业和设计师们最为关注的焦点之一。 面对日益复杂的数据泄露风险&#xff0c;如何有效地保护图纸文件的安全呢&#xff1f; 下面&#xff0c;我们就来探讨一下2024图纸加密软件的…

Python的10个文件对比与合并高效策略

文末赠免费精品编程资料~~ 在日常编程或数据分析工作中&#xff0c;经常需要处理多个文件的对比与合并任务。Python因其强大的文件处理能力和丰富的库支持&#xff0c;成为了处理这类任务的理想选择。下面&#xff0c;我们将逐步探索10种高效的文件对比与合并策略&#xff0c;…

OpenGL/GLUT实践:粒子系统,并添加纹理、动态模糊、边界碰撞(电子科技大学信软图形与动画Ⅱ实验)

源码见GitHub&#xff1a;A-UESTCer-s-Code 文章目录 1 运行效果2 实验过程2.1 基本粒子系统2.1.1 定义粒子结构2.1.2 创建粒子并初始化2.1.2.1 创建粒子2.1.2.2 初始化 2.1.3 粒子状态更新与绘制2.1.3.1 绘制2.1.3.2 更新 2.1.4 实现效果 2.2 添加纹理2.2.1 纹理添加2.2.2 渲染…

PostgreSQL + PostGIS:空间数据存储及管理解决方案

在数据库领域&#xff0c;PostgreSQL 已成为最强大、最通用的选项之一。它管理大量数据的能力、对 SQL 标准的遵守以及可扩展的架构使其受到学术界和工业界的喜爱。然而&#xff0c;真正让 PostgreSQL 脱颖而出的原因之一是它与PostGIS的集成&#xff0c;这是一个允许您有效处理…

第七课,条件表达式与初识分支判断

一&#xff0c;什么是判断 判断&#xff0c;就是在做某件事前&#xff0c;先问问满不满足条件。 进行逻辑判断&#xff0c;是生活中常见的行为。 “今天出门你要带伞吗&#xff1f;” “那得看天气怎么样&#xff0c;如果下雨或者太阳太大就带伞&#xff0c;否则就不带。”…

内存卡乱码问题解析恢复方案

一、内存卡乱码现象探析 在数字化时代&#xff0c;内存卡作为便携式数据存储设备&#xff0c;广泛应用于手机、相机、行车记录仪等多种电子设备中。然而&#xff0c;不少用户在使用过程中会遇到内存卡乱码的问题&#xff0c;即原本有序存储的文件突然变得无法识别&#xff0c;…

【前端面试】设计循环双端队列javascript

题目 https://leetcode.cn/problems/design-circular-deque/description/ 存储循环队列的向量空间是循环的,用通俗的话来讲,就是我们在做next或者prev操作时,不会发生溢出 取模、或者直接判断是否为0/size返回一个值。 数组实现 用函数来实现一个类,定义容量、头尾指针…

青远生态为云南林业规划院定制开发的自然保护地规划智能编制系统顺利通过验收

8月30日&#xff0c;青远生态为云南省林业调查规划院开发的自然保护地规划智能编制系统顺利通过验收。该系统具有智能推荐规划内容、自动生成投资估算表、智能编制规划报告等功能&#xff0c;集合了拉丁名填充、表格制作等丰富实用的工具&#xff0c;显著提升了规划工作的效率和…

电力系统有滤波器还需要装电抗器吗

在电力系统中&#xff0c;滤波器和电抗器各有不同的功能&#xff0c;尽管它们都能改善电力质量。是否需要同时安装滤波器和电抗器&#xff0c;取决于系统的具体需求和现状。以下是一些考虑因素&#xff1a; 1、滤波器的功能&#xff1a; 谐波滤波&#xff1a;滤波器主要用于抑…

基于vue框架的超市会员管理系统设计与实现xeb8c(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;会员,商品分类,商品信息,订单信息,积分等级,礼品信息,礼品兑换 开题报告内容 基于Vue框架的超市会员管理系统设计与实现开题报告 一、研究背景与意义 随着消费者对个性化服务和优惠活动需求的增加&#xff0c;超市会员管理成为提升顾…

Docker安装及验证,小白必备

Docker安装 本教程以centos系统为例 1、Docker安装前准备工作 切换国内源 cp -a /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak #备份设置为华为云的yum wget -O /etc/yum.repos.d/CentOS-Base.repo https://repo.huaweicloud.com/repository…

专用于理解游戏场景的开源大模型-VideoGameBunny

大模型在游戏开发领域扮演了重要角色&#xff0c;从AI机器人生成到场景搭建覆盖各个领域。但在游戏场景理解、图像识别、内容描述方面很差。 为了解决这些难题&#xff0c;加拿大阿尔伯塔的研究人员专门开源了一款针对游戏领域的大模型VideoGameBunny&#xff08;以下简称“VG…

7-8月月报 | Apache SeaTunnel社区进展一览

各位热爱 Apache SeaTunnel 的小伙伴们&#xff0c;社区 7-8 月份月报来啦&#xff01;这两个月项目有了哪些进展&#xff1f;又有谁登上了我们社区的贡献者榜单呢&#xff1f;快来一睹为快吧。 Merge Stars 感谢以下小伙伴上两个月为 Apache SeaTunnel 项目和社区发展所做的…

非时序检查(Non-Sequential Check)

单元或宏&#xff08;macro&#xff09;的库文件可以将时序弧指定为非时序&#xff08;non-sequential&#xff09;检查&#xff0c;例如两个数据引脚之间的时序弧。非时序检查是指两个引脚之间的检查&#xff0c;两者都不是时钟。一个引脚是约束引脚&#xff0c;其作用类似于数…

WPF在MVVM架构下使用DataGrid并实现行删除

一、效果演示 二、Model创建 //User&#xff1a;用于绑定DataGrid控件的数据 private ObservableCollection<User> _users new ObservableCollection<User>();public ObservableCollection<User> Users{get { return _users; }set { _users value; }}//Sel…

day43|打家劫舍系列 198.打家劫舍 213. 打家劫舍 II 337.打家劫舍 III

文章目录 前言198.打家劫舍思路方法一213. 打家劫舍 II思路方法一337.打家劫舍 III思路方法一方法二 暴力搜索和记忆化递推总结前言 198.打家劫舍 思路 非常直接的思路 dp五部曲 dp极其下标含义:**考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。**考虑的意思是…

IP地址安全与隐私保护

在当今数字化时代&#xff0c;IP地址作为网络设备的唯一身份标识&#xff0c;在网络安全与隐私保护中扮演着至关重要的角色。然而&#xff0c;随着网络技术的飞速发展&#xff0c;IP地址也面临着诸多挑战&#xff0c;对用户的隐私和网络安全构成了潜在威胁。本文将对IP地址在网…

JAVA基础:封装、继承和多态(详讲)

1 封装 面向对象的三大特征 &#xff1a; 封装&#xff0c; 继承&#xff0c; 多态 。 封装可以从三个层面理解 将属性和方法组合在一起&#xff08;封闭在一起&#xff09; 将属性隐藏起来&#xff0c; 对外提供可以间接操作属性的方法。&#xff08;提高程序设计安全性&…

CephFS使用

CephFS使用 一、CephFS架构二、部署CepfFS服务1、部署MDS服务2、创建CephFS metadata和data存储池3、创建cephFS并验证4、创建客户端账户5、安装ceph客户端并同步认证文件6、内核空间挂载ceph-fs6.1 客户端通过key文件挂载6.2 开机自动挂载 7、客户端模块挂载7.1 用户空间挂载c…