鸿蒙「TaskPool|Worker」多线程并发使用详解,这一篇足够!

news2024/12/29 0:07:04

概念介绍

鸿蒙的多线程并发TaskPoolWorker,他们具有相同内存模型,线程间隔离内存不共享。在项目中若使用到,有几个较重要的条件或特点这里简单作出列举。

CPU密集型任务,说白了是计算型耗时任务;
I/O密集型任务,说白了是读写型耗时任务;
官方文档重点介绍了这两种基于多线程并发机制处理任务类型,我们是要深度思考下的?!「很有意义」,是否已经包含且指明了我们使用「TaskPool/Worker」来解决项目问题的方案呢?

  • Worker

    • 使用Worker,创建线程个数最多是64个。超过则创建失败。
    • 使用Worker,传输序列化数据大小限制在16MB。
    • 引用HAR/HSP前,首先要配置对HAR/HSP的依赖。不支持 跨HAP使用Worker线程文件。
    • 使用Worker模块时,需要在主线程中注册onerror接口,否则当worker线程出现异常时会发生jscrash问题。
    • 任务执行时长上限,无限制。
  • TaskPool

    • TaskPool内部会动态调整线程个数,不支持设置数量。
    • TaskPool线程池的数量会根据硬件条件、任务负载等情况动态调整。
    • 任务执行时长上限,为3分钟(执行耗时不能超过3分钟)。
    • Promise不支持跨线程传递,不能作为concurrent function的返回值。

使用详解

对Worker以及TaskPool的使用详解,个人准备以一种特别的角度来详述。从我个人初始接触及学习研究的视角,针对 如何选用如何创建如何使用注意事项条件限制 多个方面,全面剖析

选用

依据限制条件,若考虑到任务执行时间已超过3分钟,传输数据不大。且需创建的线程个数仅几个这样子,考虑选用Worker;若考虑到任务执行时间较短,且会有大量的线程需要创建、销毁和复用,不想手动对线程数量的控制,可考虑选用TaskPool。如果使用条件上,都未超出两种限制条件,那么请随意。

创建|使用

Worker创建

Worker在进行创建使用时,有手动和自动两种方式,自动的较简单。手动创建Worker线程目录及文件时,还需同步进行相关配置。 「注意事项」 Worker线程文件需要放在"{moduleName}/src/main/ets/"目录层级之下,否则不会被打包到应用中。

自动操作:moduleName目录下任意位置,点击鼠标右键 > New > Worker,即可自动生成Worker的模板文件及配置信息,无需再手动在build-profile.json5中进行相关配置。

自动创建演示:假如创建Worker文件 entry/src/main/ets/workers/MyTestWorker1.ets ; 「workers是我自己建的文件目录」则同时deveco studio将在build-profile.json5文件中发现自动配置信息如下

/// build-profile.json5
{
  "apiType": "stageMode",
  "buildOption": {
    "sourceOption": {
      "workers": [ // 这里就是自动生成的worker配置;换言之,手动创建Worker的话,需要在这里配置下信息
        './src/main/ets/workers/MyTestWorker1.ets'
      ]
    }
  },
  "targets": [
    {
      "name": "default",
      "runtimeOS": "HarmonyOS"
    }
  ]
}

创建MyTestWorker1.ets文件,自动生成Worker文件模板如下

手动创建Worker文件的话,文件内容如监听方法和错误捕捉方法的监听,需要仿照模板编写。同时要在build-profile.json5文件中配置下配置信息。

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

const workerPort: ThreadWorkerGlobalScope = worker.workerPort;

/**
 * Defines the event handler to be called when the worker thread receives a message sent by the host thread.
 * [当工作线程接收到主线程发送的消息时,onmessage将被触发]
 * The event handler is executed in the worker thread.
 * [onmessage将在工作线程中被执行]
 * @param e message data
 */
workerPort.onmessage = (e: MessageEvents) => {
}

/**
 * Defines the event handler to be called when the worker receives a message that cannot be deserialized.
 * [当工作线程接收到无法反序列化的消息时,onmessageerror将被触发]
 * The event handler is executed in the worker thread.
 *[onmessageerror将在工作线程中被执行]
 * @param e message data
 */
workerPort.onmessageerror = (e: MessageEvents) => {
}

/**
 * Defines the event handler to be called when an exception occurs during worker execution.
 * [在工作线程执行期间发生异常时,将会调用onerror程序]
 * The event handler is executed in the worker thread.
 *[onerror将在工作线程中被执行]
 * @param e error message
 */
workerPort.onerror = (e: ErrorEvent) => {
}
Worker使用

Worker多线程并发,在创建工作线程文件后。在主线程中发起对工作线程调用时,传入文件地址作为入参。若传入地址不对,则报错提示:

Error message:The worker file path is invalid path, the file path is invaild, can't find the file.
Error code:
SourceCode:
        const workerThread: worker.ThreadWorker = new worker.ThreadWorker('entry/src/main/ets/workers/MyTestWorker1.ets');

在这里插入图片描述

「注意事项」 如何传入正确工作线程文件路径?
工作线程创建过程解释说明,

  • 先在entry/src/main/ets 目录下创建了workers文件目录,(「注意事项」 Worker线程文件需要放在"{moduleName}/src/main/ets/"目录层级之下,否则不会被打包到应用中。)
  • 然后在该workers文件目录下新建工作线程文件MyTestWorker1.ets,
  • 此时build-file.json5中自动配置信息是 "workers": ['./src/main/ets/workers/MyTestWorker1.ets' ]
  • 但不能直接使用这个配置的信息地址作为入参,而实际应该传入的入参地址,示例「可自行对比区别」: const workerThread: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/MyTestWorker1.ets');

手动编写一个简单DEMO,在UI界面onPageShow生命周期方法中创建Worker实例对象,构造方法中传入Worker线程文件的路径。实现主线程和worker工作线程之间的通信逻辑。
使用Worker实现多线程并发这一能力,首要要搞懂哪里写执行耗时任务在哪里开启且如何开启线程 这就和安卓起一个Thread线程的思路很像。

「使用要点」「注意事项」

worker.ThreadWorker实例对象是向Worker线程文件发送消息或接收Worker线程文件发送的消息。说白了就是开启线程执行的位置,如通过postMessage发送消息。即可通知worker线程文件中方法执行耗时任务。
worker.ThreadWorker就是用来开启线程工作用的。

workerThread.onmessage方法,监听并接收Worker线程文件发出的消息。比如耗时线程工作完成了,需要告知主线程此时此刻的进度
workerThread.postMessage方法,向Worker线程文件发送消息。比如通知耗时线程开始工作

/// 下面是部分主要代码,为方便阅读非重要内容已省略
/// ets/pages/HomePage.ets
import { worker, MessageEvents, ErrorEvent } from '@kit.ArkTS';

@Entry
@Component
struct HomePage {

  onPageShow(): void {
    // constructor(scriptURL: string, options?: WorkerOptions);
    // 构造方法入参Worker线程文件路径「关注」
    const workerThread: worker.ThreadWorker = new worker.ThreadWorker('entry/src/main/ets/workers/MyTestWorker1.ets');
    // 「通信」这里接收Worker线程文件中发出的消息
    workerThread.onmessage = ((event: MessageEvents) => {
      const type = event.data.type as number;
      if (type === 2) { // 这里是的匹配工作,是由worker工作线程通过workerPort.postMessage({type: 2, ..})发来的信息。
        console.error("打印日志,worker主线程收到worker工作线程发来的消息:", event.data.value)
      }
    })
    workerThread.onerror = ((event: ErrorEvent) => {})
	// 「通信」这里向Worker线程文件发送消息
    workerThread.postMessage({ 'type': 1, value: '「主线程数据包」' })
  }

  build() {
    Stack({ alignContent: Alignment.Top }) {
    ... 省略...

「使用要点」「注意事项」

worker.workerPort实例对象是主线程发送消息或接收主线程发送的消息。worker.workerPort就是用来执行耗时任务工作用的。

workerPort.onmessage方法,监听并接收主线程发出的消息。
workerPort.postMessage方法,向主线程发送消息。

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

const workerPort: ThreadWorkerGlobalScope = worker.workerPort;

/**
 * Defines the event handler to be called when the worker thread receives a message sent by the host thread.
 * The event handler is executed in the worker thread.
 *
 * @param e message data
 */
workerPort.onmessage = (e: MessageEvents) => {
  const type = e.data.type as number;
  // 耗时操作,逻辑处理等
  if (type === 1) {
    console.error("打印日志,worker工作线程收到主线程发来的消息:", e.data.value)
    workerPort.postMessage({type: 2, value: '「工作线程数据包」'}) // 通知zhuworker线程
  }
}
...省略...

运行上面源码执行结果
在这里插入图片描述

TaskPool创建|使用

TaskPool在创建及使用上较Worker则过度简单了,等同于拿来即用。
进入TaskPool#execute方法源码,(即从下面截图中)看到在taskpool命名空间中定义有三个重载的方法在提供使用
在这里插入图片描述

function execute(func: Function, ...args: Object[]): Promise<Object>;
function execute(task: Task, priority?: Priority): Promise<Object>;
function execute(group: TaskGroup, priority?: Priority): Promise<Object[]>;

针对这三种重载方法创建执行方式,没有太合适且简单易懂的示例代码做演示。就现场手动码一段正确代码演示下


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

/**工作任务,用来执行耗时操作:CPU/IO密集型*/
// 「提示」1,需要加装饰器@Concurrent;2,需要function关键字;3,需要声明在@Component外;4,返回值须要是值类型。
@Concurrent
async function taskMethod1 (): Promise<number> {
  // return Promise.resolve(9) // 不支持。Promise.resolve仍是Promise,其状态是pending,无法作为返回值使用。
  return 1; // 返回值仅能是「携带值res引用,如const res = [1,2,3]」值类型
}
/**工作任务,用来执行耗时操作:CPU/IO密集型*/
@Concurrent
async function taskMethod2 (): Promise<string>  {
  return 'hello world'; // 返回值仅能是「携带值res引用,如const res = [1,2,3]」值类型
}

@Entry
@Component
struct HomePage {

   onPageShow(): void {
     this.execute()
  }

  async execute() {

    // function execute(func: Function, ...args: Object[]): Promise<Object>;
    const resultMethod = await taskpool.execute(taskMethod1, taskMethod2)

    // function execute(task: Task, priority?: Priority): Promise<Object>;
    const task1 = new taskpool.Task('任务名称「非必填」', taskMethod1)
    const resultTask = await taskpool.execute(task1, taskpool.Priority.HIGH)

    // function execute(group: TaskGroup, priority?: Priority): Promise<Object[]>;
    const group1 = new taskpool.TaskGroup('定义任务组名称「非必填」') // TaskGroup有两个构造方法,一个无参,一个有字符串入参
    group1.addTask(taskMethod1) // 向任务组中添加任务方法的引用
    group1.addTask(taskMethod2) // 向任务组中添加任务方法的引用
    const resultGroup = await taskpool.execute(group1, taskpool.Priority.HIGH)

    console.error('打印输出:', resultMethod, resultTask, resultGroup)

  }

  build() {
    Stack({ alignContent: Alignment.Top }) {
...省略...

「注意事项」「提示」实现任务的函数需要①使用装饰器@Concurrent标注,且②仅支持在.ets文件中使用③方法需要function修饰
如果在@Component内部创建任务,会提示报错The @Concurrent decorator can decorate only common functions and async functions. <ArkTSCheck>,因此④需要在@Component外部创建才可以

「注意事项」「另外」如果在使用装饰器@Concurrent标注的任务方法中调用了某类的方法类.方法名(args) 或 类实例.方法名(args),①声明类须使用装饰器@Sendable标注。如果不是②通过import方式导入使用,则提示报错:Only imported variables and local variables can be used in @Concurrent decorated functions. <ArkTSCheck>

  • 未使用import引入时,错误提示截图图示

在这里插入图片描述

正确通过import方式引入并调用方法的方式,如下。


/**声明类,任务方法中将引用该类SendableTask,使用装饰器@Sendable修饰*/
@Sendable
export default class SendableTask {
  private static instance: SendableTask = new SendableTask();
  static getInstance(): SendableTask {
    // 获取单例
    return SendableTask.instance;
  }
  // 声明模拟一个方法
  static oneSyncMethod(): number {
    return 10;
  }
  oneMethod(): number {
    return 2;
  }
}

下面的截图中张贴了具体的调用逻辑,如何在任务方法@Concurrent async function taskMethod1中对使用外部类方法SendableTask.oneSyncMethod调用,
在这里插入图片描述
接下来跑下DEMO程序,执行TaskPool多线程并发代码,运行结果输出如截图所示:
在这里插入图片描述

08-14 14:43:34.406  45415-45415  A03D00/JSAPP  pid-45415   E   打印输出: 10 10 10,hello world

「总结」从截图debug显示及输出日志结果,可以发现并得出结论,taskpool.execute执行并发任务的三种重载方法,在执行时,各自输出的结果来源~如下

// 输出结果值来源:结果值为func方法执行结果。
function execute(func: Function, ...args: Object[]): Promise<Object>;

// 输出结果值来源:结果值为task中调用执行的func方法执行结果。
function execute(task: Task, priority?: Priority): Promise<Object>;

// 输出结果值来源:结果值是个数组,数组中每个元素值,为每个task中调用执行的func方法执行结果。
function execute(group: TaskGroup, priority?: Priority): Promise<Object[]>;

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

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

相关文章

C# 静态方法和实例方法

一、静态成员&#xff0c;实例成员&#xff0c;静态方法&#xff0c;实例方法 静态成员就是用static修饰的字段&#xff1b; 实例成员就是没有被static修饰的字段&#xff1b; 静态方法就是用static修饰的方法&#xff1b; 实例方法就是没有被static修饰的方法&#xff1b;…

OriginPro快速上手指南:数据可视化与分析的利器

目录 OriginLab - Origin and OriginPro - Data Analysis and Graphing Softwarehttps://www.originlab.com/​编辑 一、安装与界面概览 安装 界面概览 二、基础操作 数据输入 创建图表 三、高级功能 数据分析 自动化与脚本 Origin 提供了几个小工具 四、技巧与提示…

AI编程-vscode安装“通义灵码”

“通义灵码”是一款基于阿里云通义代码大模型打造的智能编码助手 1、vscode中&#xff0c;选择插件&#xff0c;输入“tongyi” &#xff0c;弹出插件选项 2、点击install 安装 3、弹出登录提示 4、选择log in&#xff0c;弹出阿里云登录界面 登录成功后提示 5、返回vscode…

【吸引力法则】人生欲:追求深度体验与宇宙链接

文章目录 探究人生欲&#xff1a;追求深度体验与宇宙链接唤醒人生欲&#xff1a;克服配得感的三大障碍法执的压制家庭的继承 探究人生欲&#xff1a;追求深度体验与宇宙链接 在人生的广阔舞台上&#xff0c;我们时常探寻着那些能够引领我们走向更深层次成长与体验的力量。今天&…

C语言—函数递归

一、递归概念 递归其实是⼀种解决问题的⽅法&#xff0c;在C语⾔中&#xff0c;递归就是函数⾃⼰调⽤⾃⼰。下面举一个例子&#xff1a; 上述就是⼀个简单的递归程序&#xff0c;只不过上⾯的递归只是为了演⽰递归的基本形式&#xff0c;不是为了解决问题&#xff0c;代码最终…

ddos造成服务器瘫痪后怎么办

在服务器遭受DDoS攻击后&#xff0c;应立即采取相应措施&#xff0c;包括加强服务器安全、使用CDN和DDoS防御服务来减轻攻击的影响。rak小编为您整理发布ddos造成服务器瘫痪后怎么办。 当DDoS攻击发生时&#xff0c;首先要做的是清理恶意流量。可以通过云服务提供商提供的防护措…

java解析facebook的android app直投下的Referral URL

背景&#xff1a; 在facebook的应用推广中&#xff0c;一般使用两种方式&#xff0c;一种是app直投&#xff0c;一种是w2a。 app直投就是用户点击广告直接跳转到应用商店进行下载应用 w2a就是通过落地页方式引导用户进行应用下载 在w2a模式下&#xff0c;可以通过落地页链接…

Repeat方法:取模运算教材与Unity控制台输出数值不同的原因

学习该知识点的参考教材&#xff1a;Unity API解析/陈宏泉编著.——北京&#xff1a;人民邮电出版社&#xff0c;2014.9 编辑脚本的环境&#xff1a;Visual Studio 2022 在学习该本教材的第五章Mathf类的内容&#xff0c;通过跟随教材上的代码了解不同UnityAPI的具体用法时&a…

【数据结构】七、查找:1.查找的概念、线性结构查找(顺序、折半(二分)、插值、稠密、分块、倒排)

一、查找Search 文章目录 一、查找Search1.查找的基本概念1.1基本概念1.2算法评价标准 二、线性结构1.顺序表查找❗1.1顺序查找1.1.1算法思想1.1.2顺序查找效率分析 2.有序表查找❗2.1折半查找2.1.1算法思想2.1.2判定树构造2.1.3通过判定树进行查找效率分析2.1.4被查找概率不相…

ReactNative笔记(自用)

环境 ios更换gem镜像源&#xff1a; 查看当前源: gem sources -l 移除默认源: gem sources --remove https://rubygems.org/。添加新的源: 添加 Ruby China 的镜像源&#xff1a; gem source -a https://gems.ruby-china.com/或者添加其他镜像源。 清华大学的gem源: htt…

进阶-1.存储引擎

存储引擎 存储引擎1.MySQL体系结构2.存储引擎简介3.存储引擎特点3.1 InnoDB3.2 MyISAM3.3 Memory 4. 存储引擎选择 存储引擎 1.MySQL体系结构 2.存储引擎简介 存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现。存储引擎是基于表的&#xff0c;而不是基于库的&…

【数学分析笔记】第2章第1节实数系的连续性(1)

2. 数列极限 2.1 实数系的连续性 人类对数系认识的历史&#xff1a; 人类最早对数系的认识是自然数集合 N \mathbb{N} N&#xff0c;自然数系对加法和乘法是封闭的&#xff08;这里的封闭是指&#xff1a;若 m ∈ N , n ∈ N ⇒ m n ∈ N , m n ∈ N m\in\mathbb{N},n\in\ma…

芯片用什么胶粘接牢固?​​​​​​​

芯片用什么胶粘接牢固?芯片粘接胶的牢固性对于电子产品的性能和可靠性至关重要。选择合适的胶水可以确保芯片能够稳定、可靠地固定在基板上。芯片的粘接通常涉及几种不同类型的胶水&#xff0c;每种胶水都有其特定的应用场景和性能特点。以下是几种常见的用于芯片粘接的胶水类…

C Primer Plus第十二章编程练习

第十二章编程练习 第一题要求我们不使用全局变量&#xff0c;修改程序12.4&#xff0c;那我们就用指针作为参数去传递变量的地址&#xff0c;去修改变量的值&#xff0c;完整程序代码以及运行结果如下&#xff1a; #include<stdio.h> //int units 0; void critic(int …

node卸载与安装

1.直接使用卸载程序卸载 2.安装新的Node 官网下载并执行下一步直到完成 3.Idea配置不同版本的node file->settings->Languages & Frameworks->Node.js and NPM->增加带选项->选择指定版本

ubuntu下使用docker、socket、yolov5进行图像处理数据交互记录

ubuntu下使用docker、socket、yolov5进行图像处理数据交互记录 概述&#xff1a;主要实现了在宿主机上通过8000端口传递一张图像给docker镜像&#xff0c;然后镜像中处理后&#xff0c;通过8001端口回传处理后的图像给宿主机。 第一章、构建镜像 一、dockerfile文件 1.拉取…

jQuery:配置与选择器

一&#xff0c;jQuery的配置 jQuery下载 这里以compressed为例&#xff0c;根据喜好也可以选择uncompressed版本。 进入jQuery代码中&#xff0c;右键另存为&#xff0c;保存到自己项目中&#xff1a; 导入jQuery <!DOCTYPE html> <html lang"en"><…

WO Mic 手机变身免费麦克风

目录 一、主要特点 1.支持多种连接方式 2.应用广泛 3.低延迟 4.简易配置 5.自动连接 6.音频格式 二、软件下载 三、软件安装 四、系统连接 五、测试 直播的时候,上课的时候,会议的时候……突然发现没有麦克风或者电脑麦克风有故障,这可怎么办呢?今天给大家介绍一…

ONNX深入研究(1):AI模型优化与加速方法

得益于现有的硬件和加速器&#xff0c;深度学习在 2010 年代初开始兴起&#xff0c;在这种支持下&#xff0c;研究人员和工程师提出了更复杂、更大的模型。然而&#xff0c;内存消耗和执行时间等限制仍然是一个挑战。由于计算资源的限制&#xff0c;这些挑战在工程和商业领域变…

UE5学习笔记11-为拿取武器添加动画

一、一点说明 动画实例通过扩展为所有机器上的每个字符都存在动画蓝图&#xff0c;动画实例只能访问该计算机上的变量。 二、思路 我在武器组件中有一个武器类的指针&#xff0c;判断当前指针是否为空去判断当前角色是否装备武器 三、实现 1.在角色C类中添加是否装备武器的函…