这一节了解一下鸿蒙中的Worker和TaskPool,Worker和TaskPool的作用是为应用程序提供一个多线程的运行环境,用于处理耗时的计算任务或其他密集型任务。可以有效地避免这些任务阻塞主线程,从而最大化系统的利用率,降低整体资源消耗,并提高系统的整体性能。
1 Worker
Worker 线程是与主线程并行的独立线程,其主要作用是为应用程序提供一个多线程的运行环境来处理耗时任务从而避免阻塞主线程的运行。创建 Worker 的线程称之为宿主线程,Worker自身的线程称之为 Worker 线程,创建 Worker 传入的 url 文件在 Worker 线程中执行,Worker 线程创建后不会主动销毁,若不处于任务状态一直运行,在一定程度上会造成资源的浪费,应及时关闭空闲的 Worker;创建Worker的线程称为宿主线程(不一定是主线程,工作线程也支持创建Worker子线程),Worker自身的线程称为Worker子线程(或Actor线程、工作线程)。每个Worker子线程与宿主线程拥有独立的实例,包含基础设施、对象、代码段等,因此每个Worker启动存在一定的内存开销,需要限制Worker的子线程数量。Worker子线程和宿主线程之间的通信是基于消息传递的,Worker通过序列化机制与宿主线程之间相互通信,完成命令及数据交互。
Worker在鸿蒙中的优点:
1 多线程运行环境:
Worker为应用程序提供了多线程的运行环境,使得应用程序能够在与主线程分离的后台线程中执行耗时操作。
适合于处理计算密集型或高延迟的任务,如大数据处理、复杂算法运算等,从而释放主线程以处理用户交互和其他即时任务。
2 避免主线程阻塞:
由于Worker在独立的线程中运行,因此不会因执行耗时操作而阻塞主线程,这有助于保持应用程序的响应性和流畅性。
3 内存模型安全:
Worker线程之间以及Worker线程与宿主线程之间的内存是不共享的,每个Worker线程拥有独立的实例,包含基础设施、对象、代码段等,从而保证了操作的安全性。
4 通信机制稳定:
Worker与宿主线程之间通过消息传递进行通信,使用序列化机制完成命令及数据交互,确保了稳定的数据传输。
Worker在鸿蒙中的缺点:
1 生命周期管理:
开发者必须手动管理Worker的生命周期,包括创建和销毁操作,这可能会增加开发的复杂性。
2 线程数量限制:
在任何时候,一个进程最多只能拥有8个同时运行的Worker线程,这个数量的限制可能会影响应用程序处理大量并发任务的能力。
3不支持任务优先级:
Worker不支持设置任务的优先级,这在一些需要区分任务执行优先级的场景中可能是一个缺陷。
栗子:
在 entry 的 ets 目录下创建 worker 目录,然后在 worker 目录下创建 worker.ts 文件,内容如下:
import worker from '@ohos.worker';
let TAG = "WORKER_THREAD";
const workerPort = worker.workerPort;
//监听宿主线程发送过来的消息
workerPort.onmessage = function (data) {
// 模拟一个耗时任务
setTimeout(() => {
workerPort.postMessage("Hello, Worker, I'm child thread");
}, 2000);
}
// 监听异常情况
workerPort.onmessageerror = function (error) {
console.log(`${TAG}, workerPort.onmessageerror: ` + JSON.stringify(error));
}
配置:
在 entry 目录下的 build-profile.json5 文件的 buildOption 项中添加对 worker 的配置,否则不起作用,配置代码如下:
{
"apiType": 'stageMode',
"buildOption": { // buildOption 下添加
"sourceOption": { // sourceOption 下添加
"workers": [ // worker 文件的路径集
"./src/main/ets/worker/worker.ts" // 路径一定要配置正确
]
}
}
}
宿主端代码:
import worker from '@ohos.worker';
let TAG = "MAIN_THREAD";
let URL = "entry/ets/worker/worker.ts";
@Entry @Component struct Index {
@State message: string = 'Hello World';
private workerInstance: worker.ThreadWorker;
private initWorker() {
this.workerInstance = new worker.ThreadWorker(URL, {
name: "WORKER_THREAD"
})
}
private monitorMessage() {
this.workerInstance.onmessage = function (data) {
console.log(`${TAG}, workerInstance.onMessage: ` + JSON.stringify(data));
}
}
private sendMessage() {
this.workerInstance.postMessage("Hello, Worker, I'm main thread");
}
private destroyWorker() {
this.workerInstance.terminate();
}
build() {
Column({space: 8}) {
Button("Init Worker")
.height(80)
.fontSize(20)
.onClick(() => {
this.initWorker();
})
Button("Monitor Worker Message")
.height(80)
.fontSize(20)
.onClick(() => {
this.monitorMessage();
})
Button("Send Worker Message")
.height(80)
.fontSize(20)
.onClick(() => {
this.sendMessage();
})
Button("Destroy Worker")
.height(80)
.fontSize(20)
.onClick(() => {
this.destroyWorker();
})
}
.width('100%')
.height('100%')
.padding(20)
}
}
2 TaskPool
ArkUI 开发框架在 @ohos.taskpool 模块里提供了多线程的运行环境,作用和其它语言里的线程池一样,都是为了降低资源的消耗、提高系统的性能且无需关心线程实例的生命周期.
TaskPool优点:
1 提高系统性能:
TaskPool通过管理和调度多个工作线程来降低整体资源消耗,从而提高系统的整体性能。
当待执行的任务数量多于工作线程时,TaskPool会根据负载均衡机制自动扩容,增加工作线程以减少整体等待时长。
相反,当活跃任务数量减少,空闲工作线程过多时,TaskPool会自动缩减工作线程数量,以优化资源利用。
2 简化线程管理:
开发者无需关心线程实例的生命周期管理,TaskPool负责创建和销毁工作线程,简化了并发编程的复杂性。
3 灵活控制任务:
开发者可以创建数量不受限制的任务(尽管由于内存限制不建议过量创建),并且可以对任务进行执行、取消等操作。
TaskPool支持为任务设置不同的优先级,满足不同场景下对任务执行顺序的精细控制需求。
4 支持大规模数据传输:
TaskPool API支持大规模的数据传输,虽然存在数据量大小的限制(如16MB),但对于大多数应用来说已经足够使用。
5 易于错误处理:
TaskPool API以数字形式返回错误码,便于开发者识别和处理错误情况,提高了问题诊断的效率。
TaskPool缺点:
1 不适宜执行阻塞操作:
不建议在TaskPool中执行阻塞操作,尤其是无限期的阻塞操作,因为这会占用工作线程,可能导致其他任务调度受阻,影响应用性能。
2 任务优先级管理复杂:
虽然TaskPool支持任务优先级设置,但在大量任务和复杂的优先级管理需求面前,可能会增加管理和调试的难度。
3 错误处理机制有限:
虽然有错误码返回,但TaskPool的错误处理机制相对有限,对于复杂的异常情况可能需要开发者自行实现更细致的处理逻辑。
4 任务无法重复执行:
一旦任务被执行,就无法在TaskPool中重复使用相同的任务实例进行多次执行,这限制了任务的复用性。
栗子:
@Entry
@Component
struct TaskPoolTest {
build() {
Column({space: 10}) {
Button("Create Task")
.onClick(() => {
taskPoolTest();
})
}
.width("100%")
.height("100%")
.padding(10)
}
}
async function taskPoolTest() {
let task = new taskpool.Task(func, "create task, then execute");
let val1 = await taskpool.execute(task);
console.log("taskpool.execute(task) result: " + val1);
let val2 = await taskpool.execute(func, "execute task by func");
console.log("taskpool.execute(function) result: " + val2);
}
@Concurrent
function func(args) {
console.log("func: " + args);
return args;
}