#跟着若城学鸿蒙# 线程模型及线程间通信 Emitter、Worker和TaskPool介绍
目录
前言
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。
- Emitter(发射器):Emitter主要适用于线程间的事件同步。它可以在不同的线程之间传递事件,并确保事件的顺序和同步性。通过Emitter,一个线程可以触发一个事件,然后其他线程可以监听并处理这个事件。这有助于不同线程之间的数据共享和协调。
- Worker(工作者):Worker主要用于新开一个线程执行耗时任务。当需要执行一些耗时操作时,为了不阻塞主任务的执行,可以使用Worker线程。Worker线程是在主线程的上下文中创建的独立线程,它可以执行一些耗时任务,如网络访问、文件读写等。工作线程可以与主线程并行执行,以提高应用的响应性和性能。
Stage模型只提供了主线程和Worker线程,Emitter主要用于主线程内或者主线程和Worker线程的事件同步, Worker主要用于新开一个线程执行耗时任务。
Emitter介绍
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线程中处理耗时的操作,需要注意的是,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介绍
任务池(taskpool)作用是为应用程序提供一个多线程的运行环境,降低整体资源的消耗、提高系统的整体性能,且您无需关心线程实例的生命周期。您可以使用任务池API创建后台任务(Task),并对所创建的任务进行如任务执行、任务取消的操作。理论上您可以使用任务池API创建数量不受限制的任务,但是出于内存因素不建议您这样做。此外,不建议您在任务中执行阻塞操作,特别是无限期阻塞操作,长时间的阻塞操作占据工作线程,可能会阻塞其他任务调度,影响您的应用性能。
所创建的同一优先级任务的执行顺序可以由你决定,任务真实执行的顺序与您调用任务池API提供的任务执行接口顺序一致。任务默认优先级是MEDIUM。(任务优先级机制暂未支持)
当同一时间待执行的任务数量大于任务池工作线程数量,任务池会根据负载均衡机制进行扩容,增加工作线程数量,减少整体等待时长。同样,当执行的任务数量减少,工作线程数量大于执行任务数量,部分工作线程处于空闲状态,任务池会根据负载均衡机制进行缩容,减少工作线程数量。(负载均衡机制暂未支持)
使用TaskPool
本模块首批接口从API version 9 开始支持。
import taskpool from '@ohos.taskpool';
Priority
https://blog.csdn.net/yyz_1987/article/details/135885467
表示所创建任务(Task)的优先级。(暂未支持)
系统能力: SystemCapability.Utils.Lang
|
名称
|
值
|
说明
|
| --- | --- | --- |
|
HIGH
|
0
|
任务为高优先级。
|
|
MEDIUM
|
1
|
任务为中优先级。
|
|
LOW
|
2
|
任务为低优先级。
|
Task
https://blog.csdn.net/yyz_1987/article/details/135885467
表示任务。使用以下方法前,需要先构造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
https://blog.csdn.net/yyz_1987/article/details/135885467https://blog.csdn.net/yyz_1987/article/details/135885467
execute(func: Function, ...args: unknown[]): Promise<unknown>
将待执行的函数放入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();
注意事项
https://blog.csdn.net/yyz_1987/article/details/135885467https://blog.csdn.net/yyz_1987/article/details/135885467
- 仅支持在Stage模型且module的compileMode为esmodule的project中使用taskpool api。确认module的compileMode方法:查看当前module的build-profile.json5,在buildOption中补充"compileMode": "esmodule"。
- taskpool任务只支持引用入参传递或者import的变量,不支持使用闭包变量,使用装饰器@Concurrent进行拦截。
- taskpool任务只支持普通函数或者async函数,不支持类成员函数或者匿名函数,使用装饰器@Concurrent进行拦截。
- 装饰器@Concurrent仅支持在ets文件使用,在ts文件中创建taskpool任务需使用"use concurrent"。
TaskPool和Worker的对比选择
TaskPool(任务池)和Worker的作用是为应用程序提供一个多线程的运行环境,用于处理耗时的计算任务或其他密集型任务。可以有效地避免这些任务阻塞主线程,从而最大化系统的利用率,降低整体资源消耗,并提高系统的整体性能。
实现特点对比
https://blog.csdn.net/yyz_1987/article/details/135885467https://blog.csdn.net/yyz_1987/article/details/135885467
表1 TaskPool和Worker的实现特点对比
|
实现
|
TaskPool
|
Worker
|
| --- | --- | --- |
|
内存模型
|
线程间隔离,内存不共享。
|
线程间隔离,内存不共享。
|
|
参数传递机制
|
采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。
支持ArrayBuffer转移和SharedArrayBuffer共享。
|
采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。
支持ArrayBuffer转移和SharedArrayBuffer共享。
|
|
参数传递
|
直接传递,无需封装,默认进行transfer。
|
消息对象唯一参数,需要自己封装。
|
|
方法调用
|
直接将方法传入调用。
|
在Worker线程中进行消息解析并调用对应方法。
|
|
返回值
|
异步调用后默认返回。
|
主动发送消息,需在onmessage解析赋值。
|
|
生命周期
|
TaskPool自行管理生命周期,无需关心任务负载高低。
|
开发者自行管理Worker的数量及生命周期。
|
|
任务池个数上限
|
自动管理,无需配置。
|
同个进程下,最多支持同时开启8个Worker线程。
|
|
任务执行时长上限
|
无限制。
|
无限制。
|
|
设置任务的优先级
|
不支持。
|
不支持。
|
|
执行任务的取消
|
支持取消任务队列中等待的任务。
|
不支持。
|
适用场景对比
https://blog.csdn.net/yyz_1987/article/details/135885467https://blog.csdn.net/yyz_1987/article/details/135885467
TaskPool偏向独立任务维度,该任务在线程中执行,无需关注线程的生命周期,超长任务(大于3分钟)会被系统自动回收;而Worker偏向线程的维度,支持长时间占据线程执行,需要主动管理线程生命周期。
常见的一些开发场景及适用具体说明如下:
-
有关联的一系列同步任务。例如在一些需要创建、使用句柄的场景中,句柄创建每次都是不同的,该句柄需永久保存,保证使用该句柄进行操作,需要使用Worker。
-
需要频繁取消的任务。例如图库大图浏览场景,为提升体验,会同时缓存当前图片左右侧各2张图片,往一侧滑动跳到下一张图片时,要取消另一侧的一个缓存任务,需要使用TaskPool。
-
大量或者调度点较分散的任务。例如大型应用的多个模块包含多个耗时任务,不方便使用8个Worker去做负载管理,推荐采用TaskPool。
TaskPool注意事项
https://blog.csdn.net/yyz_1987/article/details/135885467https://blog.csdn.net/yyz_1987/article/details/135885467
- 实现任务的函数需要使用装饰器https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-concurrent-0000001700975510-V2@Concurrent标注,且仅支持在.ets文件中使用。
- 实现任务的函数入参需满足序列化支持的类型,详情请参见数据传输对象。
- 由于不同线程中上下文对象是不同的,因此TaskPool工作线程只能使用线程安全的库,例如UI相关的非线程安全库不能使用。
- 序列化传输的数据量大小限制为16MB。
Worker注意事项
https://blog.csdn.net/yyz_1987/article/details/135885467https://blog.csdn.net/yyz_1987/article/details/135885467
- 创建Worker时,传入的Worker.ts路径在不同版本有不同的规则,详情请参见文件路径注意事项。
- Worker创建后需要手动管理生命周期,且最多同时运行的Worker子线程数量为8个,详情请参见生命周期注意事项。
- Ability类型的Module支持使用Worker,Library类型的Module不支持使用Worker。
- 创建Worker不支持使用其他Module的Worker.ts文件,即不支持跨模块调用Worker。
- 由于不同线程中上下文对象是不同的,因此Worker线程只能使用线程安全的库,例如UI相关的非线程安全库不能使用。
- 序列化传输的数据量大小限制为16MB。
写在最后
- 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
- 关注博主,同时可以期待后续文章ing🚀,不定期分享原创知识。
- 想要获取更多完整鸿蒙最新VIP学习资料,请关注猫哥公众号【猫青年】,回复“鸿蒙”获取
其他资源
鸿蒙开发笔记(十三): 线程模型,线程间通信,Emitter,Workder_鸿蒙 work 线程-CSDN博客
【愚公系列】2023年12月 HarmonyOS教学课程 053-Stage模型(线程模型)-CSDN博客
https://blog.csdn.net/yyz_1987/article/details/135885467
本文作者 https://blog.csdn.net/yyz_1987,转载请注明出处。
更多推荐
所有评论(0)