目录
前言
线程模型概述
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流畅性。
主线程有如下职责:
1.执行UI绘制,主线程负责处理与用户界面相关的所有操作,包括布局计算、渲染以及屏幕刷新等。在鸿蒙系统中,ArkTS引擎用于管理主线程上的UI渲染。
2.管理主线程的ArkTS引擎实例,使多个UIAbility组件能够运行在其之上。
3.管理其他线程(例如Worker线程)的ArkTS引擎实例,例如启动和终止其他线程。
4.分发交互事件,主线程接收并分发来自用户的触摸事件以及其他系统事件给相应的组件进行处理。
5.消息循环:鸿蒙系统基于消息机制实现线程间的通信和任务调度,主线程维护了一个消息队列,通过循环处理这些消息来响应不同的应用程序事件。
6.处理应用代码的回调,包括事件处理和生命周期管理。
7.接收Worker线程发送的消息。
除主线程外,还有一类与主线程并行的独立线程Worker,主要用于执行耗时操作,但不可以直接操作UI。Worker线程在主线程中创建,与主线程相互独立。最多可以创建8个Worker。
同时,在HarmonyOS应用架构中,为了保证应用的流畅性和响应性,非UI相关的耗时操作通常不会在主线程上执行,而是需要创建额外的工作线程或任务来完成。
此外,HarmonyOS采用了Stage模型,将应用划分为多个Ability模块,每个模块可能包含自己的业务逻辑线程。
HarmonyOS提供了两种线程间通信的方式,分别是Emitter和Worker。
Stage模型只提供了主线程和Worker线程,Emitter主要用于主线程内或者主线程和Worker线程的事件同步, Worker主要用于新开一个线程执行耗时任务。
Emitter主要提供线程间发送和处理事件的能力,包括对持续订阅事件或单次订阅事件的处理、取消订阅事件、发送事件到事件队列等。
比如借助Emitter可以实现类似Android中EventBus事件总线的功能。
Emitter的使用步骤如下:
1.订阅事件
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);
2、发送事件
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中不能直接更新Page。
Worker的开发步骤如下:
1、在工程的模块级build-profile.json5文件的buildOption属性中添加配置信息。
"buildOption": {
"sourceOption": {
"workers": [
"./src/main/ets/workers/worker.ts"
]
}
}
2、根据build-profile.json5中的配置创建对应的worker.ts文件。
worker.ts文件:
import worker from '@ohos.worker';
let parent = worker.workerPort;
// 处理来自主线程的消息
parent.onmessage = function(message) {
console.info("onmessage: " + message)
// 发送消息到主线程
parent.postMessage("message from worker thread.")
}
3、主线程中使用如下方式初始化和使用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)
// 根据业务按需停止worker线程
wk.terminate()
}
注意:build-profile.json5中配置的worker.ts的相对路径都为./src/main/ets/workers/worker.ts时,在Stage模型下创建worker需要传入路径 entry/ets/workers/worker.ts;
任务池(taskpool)作用是为应用程序提供一个多线程的运行环境,降低整体资源的消耗、提高系统的整体性能,且您无需关心线程实例的生命周期。您可以使用任务池API创建后台任务(Task),并对所创建的任务进行如任务执行、任务取消的操作。理论上您可以使用任务池API创建数量不受限制的任务,但是出于内存因素不建议您这样做。此外,不建议您在任务中执行阻塞操作,特别是无限期阻塞操作,长时间的阻塞操作占据工作线程,可能会阻塞其他任务调度,影响您的应用性能。
所创建的同一优先级任务的执行顺序可以由你决定,任务真实执行的顺序与您调用任务池API提供的任务执行接口顺序一致。任务默认优先级是MEDIUM。(任务优先级机制暂未支持)
当同一时间待执行的任务数量大于任务池工作线程数量,任务池会根据负载均衡机制进行扩容,增加工作线程数量,减少整体等待时长。同样,当执行的任务数量减少,工作线程数量大于执行任务数量,部分工作线程处于空闲状态,任务池会根据负载均衡机制进行缩容,减少工作线程数量。(负载均衡机制暂未支持)
本模块首批接口从API version 9 开始支持。
import taskpool from '@ohos.taskpool';
表示所创建任务(Task)的优先级。(暂未支持)
系统能力: SystemCapability.Utils.Lang
名称 |
值 |
说明 |
---|---|---|
HIGH |
0 |
任务为高优先级。 |
MEDIUM |
1 |
任务为中优先级。 |
LOW |
2 |
任务为低优先级。 |
表示任务。使用以下方法前,需要先构造Task。
constructor
constructor(func: Function, ...args: unknown[])
Task的构造函数。
系统能力: SystemCapability.Utils.Lang
参数:
参数名 |
类型 |
必填 |
说明 |
---|---|---|---|
func |
Function |
是 |
任务执行需要传入函数,支持的函数返回值类型请查序列化支持类型。 |
args |
unknown[] |
否 |
任务执行传入函数的参数,支持的参数类型请查序列化支持类型。默认值为undefined。 |
错误码:
以下错误码的详细介绍请参见语言基础类库错误码。
错误码ID |
错误信息 |
---|---|
10200014 |
The function is not mark as concurrent. |
@Concurrent
function func(args) {
console.log("func: " + args);
return args;
}
let task = new taskpool.Task(func, "this is my first Task");
taskpool.execute
execute(func: Function, ...args: unknown[]): Promise
将待执行的函数放入taskpool内部任务队列等待,等待分发到工作线程执行。当前执行模式不可取消任务。
@Concurrent
function func(args) {
console.log("func: " + args);
return args;
}
async function taskpoolTest() {
let value = await taskpool.execute(func, 100);
console.log("taskpool result: " + value);
}
taskpoolTest();
TaskPool(任务池)和Worker的作用是为应用程序提供一个多线程的运行环境,用于处理耗时的计算任务或其他密集型任务。可以有效地避免这些任务阻塞主线程,从而最大化系统的利用率,降低整体资源消耗,并提高系统的整体性能。
表1 TaskPool和Worker的实现特点对比
实现 |
TaskPool |
Worker |
---|---|---|
内存模型 |
线程间隔离,内存不共享。 |
线程间隔离,内存不共享。 |
参数传递机制 |
采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。 支持ArrayBuffer转移和SharedArrayBuffer共享。 |
采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。 支持ArrayBuffer转移和SharedArrayBuffer共享。 |
参数传递 |
直接传递,无需封装,默认进行transfer。 |
消息对象唯一参数,需要自己封装。 |
方法调用 |
直接将方法传入调用。 |
在Worker线程中进行消息解析并调用对应方法。 |
返回值 |
异步调用后默认返回。 |
主动发送消息,需在onmessage解析赋值。 |
生命周期 |
TaskPool自行管理生命周期,无需关心任务负载高低。 |
开发者自行管理Worker的数量及生命周期。 |
任务池个数上限 |
自动管理,无需配置。 |
同个进程下,最多支持同时开启8个Worker线程。 |
任务执行时长上限 |
无限制。 |
无限制。 |
设置任务的优先级 |
不支持。 |
不支持。 |
执行任务的取消 |
支持取消任务队列中等待的任务。 |
不支持。 |
TaskPool偏向独立任务维度,该任务在线程中执行,无需关注线程的生命周期,超长任务(大于3分钟)会被系统自动回收;而Worker偏向线程的维度,支持长时间占据线程执行,需要主动管理线程生命周期。
常见的一些开发场景及适用具体说明如下:
实现任务的函数需要使用装饰器@Concurrent标注,且仅支持在.ets文件中使用。
实现任务的函数入参需满足序列化支持的类型,详情请参见数据传输对象。
由于不同线程中上下文对象是不同的,因此TaskPool工作线程只能使用线程安全的库,例如UI相关的非线程安全库不能使用。
序列化传输的数据量大小限制为16MB。
创建Worker时,传入的Worker.ts路径在不同版本有不同的规则,详情请参见文件路径注意事项。
Worker创建后需要手动管理生命周期,且最多同时运行的Worker子线程数量为8个,详情请参见生命周期注意事项。
Ability类型的Module支持使用Worker,Library类型的Module不支持使用Worker。
创建Worker不支持使用其他Module的Worker.ts文件,即不支持跨模块调用Worker。
由于不同线程中上下文对象是不同的,因此Worker线程只能使用线程安全的库,例如UI相关的非线程安全库不能使用。
序列化传输的数据量大小限制为16MB。
鸿蒙开发笔记(十三): 线程模型,线程间通信,Emitter,Workder_鸿蒙 work 线程-CSDN博客
【愚公系列】2023年12月 HarmonyOS教学课程 053-Stage模型(线程模型)-CSDN博客
https://rtos_yuan.gitee.io/oh_app/#/custom_comon_event
文档中心--TaskPool和Worker
文档中心[email protected](使用任务池)