HarmonyOS 鸿蒙应用开发 线程模型及线程间通信:Emitter、Worker和TaskPool介绍
HarmonyOS 鸿蒙应用开发 线程模型及线程间通信:Emitter、Worker和TaskPool介绍
<markdown _ngcontent-psr-c147="" class="markdownPreContainer">
目录
- 前言
- 线程模型概述
- Emitter 介绍
- Worker 介绍
- TaskPool 介绍
- 使用 TaskPool
- Priority
- Task
- 示例
- 注意事项
- TaskPool 和 Worker 的对比选择
- 实现特点对比
- 适用场景对比
- TaskPool 注意事项
- Worker 注意事项
- 写在最后
- 其他资源
前言
HarmonyOS(鸿蒙系统)应用的线程模型设计考虑了系统的性能优化和用户体验。在鸿蒙应用开发中,每个进程都有一个主线程(UI 线程)。主推的应用架构采用 Stage 模型,该模型以场景为中心,将应用划分为不同的 Stage(阶段)或 Ability(能力)。每个 Ability 可以理解为一个独立的功能模块,它可以是页面(Page Ability)、服务(Service Ability)或者其他类型的能力。每个 Ability 有自己的生命周期,并且可以在需要时动态加载和卸载,以此提高系统的资源利用率和响应速度。
线程模型概述
在 HarmonyOS 应用中,每个进程都会有一个主线程(UI 线程),用于处理 UI 更新、事件分发等操作。对于耗时任务,开发者需要创建工作线程进行处理,以避免阻塞主线程影响 UI 流畅性。
主线程职责
- 执行 UI 绘制:主线程负责处理与用户界面相关的所有操作,包括布局计算、渲染以及屏幕刷新等。在鸿蒙系统中,ArkTS 引擎用于管理主线程上的 UI 渲染。
- 管理主线程的 ArkTS 引擎实例:使多个 UI Ability 组件能够运行在其之上。
- 管理其他线程的 ArkTS 引擎实例:例如启动和终止其他线程。
- 分发交互事件:主线程接收并分发来自用户的触摸事件以及其他系统事件给相应的组件进行处理。
- 消息循环:鸿蒙系统基于消息机制实现线程间的通信和任务调度,主线程维护了一个消息队列,通过循环处理这些消息来响应不同的应用程序事件。
- 处理应用代码的回调:包括事件处理和生命周期管理。
- 接收 Worker 线程发送的消息。
除主线程外,还有一类与主线程并行的独立线程 Worker,主要用于执行耗时操作,但不能直接操作 UI。Worker 线程在主线程中创建,与主线程相互独立。最多可以创建 8 个 Worker。
同时,在 HarmonyOS 应用架构中,为了保证应用的流畅性和响应性,非 UI 相关的耗时操作通常不会在主线程上执行,而是需要创建额外的工作线程或任务来完成。
此外,HarmonyOS 采用了 Stage 模型,将应用划分为多个 Ability 模块,每个模块可能包含自己的业务逻辑线程。
HarmonyOS 提供了两种线程间通信的方式,分别是 Emitter 和 Worker。
- Emitter:主要用于线程间的事件同步。它可以传递事件,并确保事件的顺序和同步性。
- Worker:主要用于新开一个线程执行耗时任务。当需要执行一些耗时操作时,为了不阻塞主任务的执行,可以使用 Worker 线程。
Emitter 介绍
Emitter 主要提供线程间发送和处理事件的能力,包括对持续订阅事件或单次订阅事件的处理、取消订阅事件、发送事件到事件队列等。
比如借助 Emitter 可以实现类似 Android 中 EventBus 事件总线的功能。
Emitter 的使用步骤
- 订阅事件
import emitter from "@ohos.events.emitter";
// 定义一个eventId为1的事件
let event = {
eventId: 1
};
// 收到eventId为1的事件后执行该回调
let callback = (eventData) => {
console.info(‘event callback’);
};
// 订阅eventId为1的事件
emitter.on(event, callback);
- 发送事件
import emitter from "@ohos.events.emitter";
// 定义一个eventId为1的事件,事件优先级为Low
let event = {
eventId: 1,
priority: emitter.EventPriority.LOW
};
let eventData = {
data: {
“content”: “c”,
“id”: 1,
“isEmpty”: false,
}
};
// 发送eventId为1的事件,事件内容为eventData
emitter.emit(event, eventData);
Worker 介绍
Worker 是与主线程并行的独立线程。创建 Worker 的线程被称为宿主线程,Worker 工作的线程被称为 Worker 线程。创建 Worker 时传入的脚本文件在 Worker 线程中执行,通常在 Worker 线程中处理耗时的操作,需要注意的是,Worker 中不能直接更新 Page。
Worker 的开发步骤
- 在工程的模块级
build-profile.json5
文件的buildOption
属性中添加配置信息。
"buildOption": {
"sourceOption": {
"workers": [
"./src/main/ets/workers/worker.ts"
]
}
}
- 根据
build-profile.json5
中的配置创建对应的worker.ts
文件。
// src/main/ets/workers/worker.ts
import worker from '@ohos.worker';
let parent = worker.workerPort;
// 处理来自主线程的消息
parent.onmessage = function(message) {
console.info("onmessage: " + message.data);
// 发送消息到主线程
parent.postMessage(“message from worker thread.”);
}
- 主线程中使用如下方式初始化和使用 Worker。
import worker from '@ohos.worker';
let wk = new worker.ThreadWorker(“entry/ets/workers/worker.ts”);
// 发送消息到 Worker 线程
wk.postMessage(“message from main thread.”);
// 处理来自 Worker 线程的消息
wk.onmessage = function(message) {
console.info("message from worker: " + message.data);
<span class="hljs-comment">// 根据业务按需停止 Worker 线程</span>
wk.<span class="hljs-title function_">terminate</span>();
}
注意:
build-profile.json5
中配置的worker.ts
的相对路径都为./src/main/ets/workers/worker.ts
时,在 Stage 模型下创建 Worker 需要传入路径entry/ets/workers/worker.ts
。
TaskPool 介绍
任务池(TaskPool)作用是为应用程序提供一个多线程的运行环境,降低整体资源的消耗、提高系统的整体性能,并且您无需关心线程实例的生命周期。您可以使用任务池 API 创建后台任务(Task),并对所创建的任务进行如任务执行、任务取消的操作。理论上您可以使用任务池 API 创建数量不受限制的任务,但是出于内存因素不建议您这样做。此外,不建议您在任务中执行阻塞操作,特别是无限期阻塞操作,长时间的阻塞操作占据工作线程,可能会阻塞其他任务调度,影响您的应用性能。
所创建的同一优先级任务的执行顺序可以由您决定,任务真实执行的顺序与您调用任务池 API 提供的任务执行接口顺序一致。任务默认优先级是 MEDIUM。(任务优先级机制暂未支持)
当同一时间待执行的任务数量大于任务池工作线程数量,任务池会根据负载均衡机制进行扩容,增加工作线程数量,减少整体等待时长。同样,当执行的任务数量减少,工作线程数量大于执行任务数量,部分工作线程处于空闲状态,任务池会根据负载均衡机制进行缩容,减少工作线程数量。(负载均衡机制暂未支持)
使用 TaskPool
本模块首批接口从 API version 9 开始支持。
import taskpool from '@ohos.taskpool';
// Priority
/**
- 表示所创建任务(Task)的优先级。(暂未支持)
*/
const PRIORITY_HIGH = 0;
const PRIORITY_MEDIUM = 1;
const PRIORITY_LOW = 2;
// Task
/**
- 表示任务。使用以下方法前,需要先构造 Task。
*/
@Concurrent
function func(args) {
console.log("func: " + args);
return args;
}
let task = new taskpool.Task(func, “this is my first Task”);
// execute
/**
- execute(func: Function, …args: unknown[]): Promise<unknown>
- 将待执行的函数放入 taskpool 内部任务队列等待,等待分发到工作线程执行。当前执行模式不可取消任务。
*/
async function taskpoolTest() {
let value = await taskpool.execute(func, 100);
console.log("taskpool result: " + value);
}
taskpoolTest();
注意事项
- 仅支持在 Stage 模型且 module 的 compileMode 为 esmodule 的 project 中使用 taskpool API。 确认 module 的 compileMode 方法:查看当前 module 的
build-profile.json5
,在buildOption
中补充"compileMode": "esmodule"
。 - TaskPool 任务只支持引用入参传递或者 import 的变量,不支持使用闭包变量,使用装饰器
[@Concurrent](/user/Concurrent)
进行拦截。 - TaskPool 任务只支持普通函数或者 async 函数,不支持类成员函数或者匿名函数,使用装饰器
[@Concurrent](/user/Concurrent)
进行拦截。 - 装饰器
[@Concurrent](/user/Concurrent)
仅支持在 .ets 文件中使用,在 .ts 文件中创建 TaskPool 任务需使用use concurrent
。
TaskPool 和 Worker 的对比选择
TaskPool(任务池)和 Worker 的作用是为应用程序提供一个多线程的运行环境,用于处理耗时的计算任务或其他密集型任务。可以有效地避免这些任务阻塞主线程,从而最大化系统的利用率,降低整体资源消耗,并提高系统的整体性能。
实现特点对比
实现 | TaskPool | Worker |
---|---|---|
内存模型 | 线程间隔离,内存不共享。 | 线程间隔离,内存不共享。 |
参数传递机制 | 标准的结构化克隆算法(Structured Clone)。 | 标准的结构化克隆算法(Structured Clone)。 |
参数传递 | 直接传递,无需封装,默认进行 transfer。 | 消息对象唯一参数,需要自己封装。 |
方法调用 | 直接将方法传入调用。 | 在 Worker 线程中进行消息解析并调用对应方法。 |
返回值 | 异步调用后默认返回。 | 主动发送消息,需在 onmessage 解析赋值。 |
生命周期 | TaskPool 自行管理生命周期,无需关心任务负载高低。 | 开发者自行管理 Worker 的数量及生命周期。 |
任务池个数上限 | 自动管理,无需配置。 | 同个进程下,最多支持同时开启8个 Worker 线程。 |
任务执行时长上限 | 无限制。 | 无限制。 |
设置任务优先级 | 不支持。 | 不支持。 |
执行任务的取消 | 支持取消任务队列中等待的任务。 | 不支持。 |
适用场景对比
- TaskPool 偏向独立任务维度,该任务在线程中执行,无需关注线程的生命周期,超长任务(大于 3 分钟)会被系统自动回收;而 Worker 偏向线程的维度,支持长时间占据线程执行,需要主动管理线程生命周期。
- 有关联的一系列同步任务。例如在一些需要创建、使用句柄的场景中,句柄创建每次都是不同的,该句柄需永久保存,保证使用该句柄进行操作,需要使用 Worker。
- 需要频繁取消的任务。例如图库大图浏览场景,为提升体验,会同时缓存当前图片左右侧各 2 张图片,往一侧滑动跳到下一张图片时,要取消另一侧的一个缓存任务,需要使用 TaskPool。
- 大量或者调度点较分散的任务。例如大型应用的多个模块包含多个耗时任务,不方便使用 8 个 Worker 去做负载管理,推荐采用 TaskPool。
TaskPool 注意事项
- 实现任务的函数需要使用装饰器
[@Concurrent](/user/Concurrent)
标注,且仅支持在 .ets 文件中使用。 - 实现任务的函数入参需满足序列化支持的类型,详情请参见数据传输对象。
- 由于不同线程中上下文对象是不同的,因此 TaskPool 工作线程只能使用线程安全的库,例如 UI 相关的非线程安全库不能使用。
- 序列化传输的数据量大小限制为 16MB。
Worker 注意事项
- 创建 Worker 时,传入的
worker.ts
路径在不同版本有不同的规则,详情请参见文件路径注意事项。 - Worker 创建后需要手动管理生命周期,且最多同时运行的 Worker 子线程数量为 8 个,详情请参见生命周期注意事项。
- Ability 类型的 Module 支持使用 Worker,Library 类型的 Module 不支持使用 Worker。
- 创建 Worker 不支持使用其他 Module 的
worker.ts
文件,即不支持跨模块调用 Worker。 - 由于不同线程中上下文对象是不同的,因此 Worker 线程只能使用线程安全的库,例如 UI 相关的非线程安全库不能使用。
- 序列化传输的数据量大小限制为 16MB。
更多关于HarmonyOS 鸿蒙应用开发 线程模型及线程间通信:Emitter、Worker和TaskPool介绍的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
更多关于HarmonyOS 鸿蒙应用开发 线程模型及线程间通信:Emitter、Worker和TaskPool介绍的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS鸿蒙应用开发中,线程模型及线程间通信机制是构建高效应用的关键。Emitter、Worker和TaskPool是鸿蒙系统中处理异步任务和线程间通信的重要组件。
Emitter用于事件的发射,当某个事件发生时,Emitter会触发相应的事件监听器。它类似于一个信号源,能够向多个监听者广播事件,实现事件驱动的编程模式。
Worker是专门用于处理耗时任务的线程。它独立于主线程运行,可以避免耗时任务阻塞主线程,保证应用的流畅性。Worker线程执行完毕后,可以通过回调或其他机制将结果返回给主线程。
TaskPool是一个任务池,用于管理和调度多个Worker线程。它将任务分配给可用的Worker线程执行,提高了任务处理的效率和资源的利用率。TaskPool还可以根据任务的优先级和类型进行智能调度,确保关键任务得到及时处理。
在鸿蒙应用开发中,开发者可以利用Emitter、Worker和TaskPool来构建复杂的异步任务和线程间通信机制。通过合理使用这些组件,可以实现应用的高效运行和良好的用户体验。
如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html