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

2024-09-03 08:20

本文主要是介绍HarmonyOS开发实战( Beta5版)线程间通信场景最佳实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

简介

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

​​

总结

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

这篇关于HarmonyOS开发实战( Beta5版)线程间通信场景最佳实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1132505

相关文章

Mysql虚拟列的使用场景

《Mysql虚拟列的使用场景》MySQL虚拟列是一种在查询时动态生成的特殊列,它不占用存储空间,可以提高查询效率和数据处理便利性,本文给大家介绍Mysql虚拟列的相关知识,感兴趣的朋友一起看看吧... 目录1. 介绍mysql虚拟列1.1 定义和作用1.2 虚拟列与普通列的区别2. MySQL虚拟列的类型2

在C#中获取端口号与系统信息的高效实践

《在C#中获取端口号与系统信息的高效实践》在现代软件开发中,尤其是系统管理、运维、监控和性能优化等场景中,了解计算机硬件和网络的状态至关重要,C#作为一种广泛应用的编程语言,提供了丰富的API来帮助开... 目录引言1. 获取端口号信息1.1 获取活动的 TCP 和 UDP 连接说明:应用场景:2. 获取硬

Golang操作DuckDB实战案例分享

《Golang操作DuckDB实战案例分享》DuckDB是一个嵌入式SQL数据库引擎,它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的,DuckDB支持各种数据类型和SQL特性... 目录DuckDB的主要优点环境准备初始化表和数据查询单行或多行错误处理和事务完整代码最后总结Duck

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

基于Python开发电脑定时关机工具

《基于Python开发电脑定时关机工具》这篇文章主要为大家详细介绍了如何基于Python开发一个电脑定时关机工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 简介2. 运行效果3. 相关源码1. 简介这个程序就像一个“忠实的管家”,帮你按时关掉电脑,而且全程不需要你多做

在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码

《在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码》在MyBatis的XML映射文件中,trim元素用于动态添加SQL语句的一部分,处理前缀、后缀及多余的逗号或连接符,示... 在MyBATis的XML映射文件中,<trim>元素用于动态地添加SQL语句的一部分,例如SET或W

Java中的Opencv简介与开发环境部署方法

《Java中的Opencv简介与开发环境部署方法》OpenCV是一个开源的计算机视觉和图像处理库,提供了丰富的图像处理算法和工具,它支持多种图像处理和计算机视觉算法,可以用于物体识别与跟踪、图像分割与... 目录1.Opencv简介Opencv的应用2.Java使用OpenCV进行图像操作opencv安装j

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

Linux中Curl参数详解实践应用

《Linux中Curl参数详解实践应用》在现代网络开发和运维工作中,curl命令是一个不可或缺的工具,它是一个利用URL语法在命令行下工作的文件传输工具,支持多种协议,如HTTP、HTTPS、FTP等... 目录引言一、基础请求参数1. -X 或 --request2. -d 或 --data3. -H 或