HarmonyOS鸿蒙Next中如何在TaskPool和Woker获取上下文Context
HarmonyOS鸿蒙Next中如何在TaskPool和Woker获取上下文Context
Worker线程和TaskPool线程中无法直接获取到组件级的Context。可以通过主线程参数传递应用级Context,通过getHostContext接口获取Context上下文。
开发者您好,可以参考以下方案实现多线程中传递上下文context。
【背景知识】
Context是应用中对象的上下文,其提供了应用的一些基础信息,例如resourceManager(资源管理)、applicationInfo(当前应用信息)、dir(应用文件路径)、area(文件分区)等,以及应用的一些基本方法,例如getApplicationContext()等。
Transferable对象,也称为NativeBinding对象,是指绑定C++对象的JS对象,主体功能由C++提供,其JS对象壳被分配在虚拟机本地堆(LocalHeap)。跨线程传输时复用同一个C++对象,相比于JS对象的拷贝模式,传输效率高。因此,可共享或转移的NativeBinding对象也被称为Transferable对象。
常见的共享模式NativeBinding对象包括Context对象,它包含应用程序组件的上下文信息,提供访问系统服务和资源的方式,使得应用程序组件可以与系统进行交互。
workerPort.postMessage和postMessageWithSharedSendable都是宿主线程向Worker发送消息的方式,但postMessage只支持序列化类型,postMessageWithSharedSendable支持序列化类型和Sendable支持的数据类型。
SendableContext符合Sendable协议,可以与Context对象相互转换,用于ArkTS并发实例间(包括主线程、TaskPool&Worker工作线程)的数据传递。
【解决方案】
一般常见多线程context传递使用场景中,仅限于对Context中的资源进行读取,如果存在读写,可能存在并发安全问题,需要用到异步锁保证并发安全,不在本文中讨论。
一、多线程中传递Context上下文对象有以下常见场景:
- 子线程读写主线程数据库,需要用到主线程Context对象,创建数据库对象,进行增删改查等操作。
- 子线程需要访问主线程中的资源文件,需要用到主线程Context对象,访问资源文件,处理相关业务。
二、多线程中传递Context上下文对象有以下方案:
方案一: 利用taskpook/worker序列化接口直接传递Context对象。
背景知识中提到Context对象是共享模式NativeBinding对象,可以在多线程间共享传递,但是由于JS对象壳被分配在虚拟机本地堆,所以传递过程还是会存在一定内存开销。
-
worker传递Context对象:
- 主线程中通过postMessage发送Context对象。
- Worker中获取Context对象操作资源文件。
-
taskpool传递Context对象:
- 先在主线程中获取对应的Context对象,通过taskpool序列化接口execute传递。
- 然后在子线程中使用Context对象。
方案二: 利用SendableContext传递。
因为SendableContext是遵循@sendable协议,在底层是进行引用传递,但是如果子线程较多,会存在多份引用,难以维护、管理。
-
worker传递SendableContext对象:
- 主线程传递将Context对象转成SendableContext,通过postMessageWithSharedSendable发送给子线程。
- Worker线程接收SendableContext对象并转回Context对象。
-
taskpool传递SendableContext对象:
- 可以将Context对象直接转成SendableContext对象传给执行函数,在执行函数中再转成Context。
方案三: 使用共享模块,实现一个单例类。在模块方法中将实例化的context并转换为SendableContext,最后导出Sendable对象,在子线程中使用。
-
实现共享模块的单例Context类:
// 共享模块sharedModule.ets import { sendableContextManager } from '[@kit](/user/kit).AbilityKit'; // 声明当前模块为共享模块,只能导出可Sendable数据 "use shared" // 共享模块,SingletonA全局唯一 [@Sendable](/user/Sendable) class SingletonA { private sendableContext: sendableContextManager.SendableContext = sendableContextManager.convertFromContext(getContext()); // 返回sendableContext对象 public getContext() { return this.sendableContext } } export let singletonA = new SingletonA();
-
在多线程业务代码中引用单例类,进行Context对象的使用:
// index.ets import { taskpool } from '[@kit](/user/kit).ArkTS'; import { singletonA } from '../util/sharedModule'; import { common, sendableContextManager } from '[@kit](/user/kit).AbilityKit'; [@Concurrent](/user/Concurrent) async function printContext() { // 将SendableContext对象转换为Context let context: common.Context = sendableContextManager.convertToContext(singletonA.getContext()); // 主线程和子线程调用时均可打正常打印 console.info('sendableContextManager:' + context.cacheDir) } [@Entry](/user/Entry) [@Component](/user/Component) struct Index { build() { Row() { Column({ space: 10 }) { Button("MainThread print") .onClick(async () => { await printContext(); }) Button("Taskpool print") .onClick(async () => { await taskpool.execute(printContext); }) } .width('100%') } .height('100%') } }
【常见FAQ】
Q:为什么通过postMessage将SendableContext传递到Worker会报错? A:postMessage只支持序列化对象。SendableContext对象是Sendable支持的数据类型,需要通过postMessageWithSharedSendable进行发送。
Q:postMessageWithSharedSendable和postMessage区别? A:postMessage:宿主线程通过转移对象所有权或者拷贝数据的方式向Worker线程发送消息。 postMessageWithSharedSendable:宿主线程向Worker线程发送消息,消息中的Sendable对象通过引用传递,非Sendable对象通过序列化传递。
【总结】
方案一 | 方案二 | 方案三 | |
---|---|---|---|
优点 | 简单易用、接口简单 | 通过引用传递,减少了程序开销 | 减少了程序开销,同时也避免引用次数过多,便于管理 |
缺点 | 直接拷贝传递Context对象存在一定程序开销 | 但是如果线程过多,线程间引用较多,难以维护 | 实现稍复杂,不适合用于context传递较少的场景 |
适用场景 | 子线程读取rawfile目录的资源文件 | 子线程读取rawfile目录的资源文件 | 子线程频繁读取主线程创建的数据库 |
对于context传递不频繁的场景,优先选择方案二;对于对于context传递频繁的场景,优先选择方案三。
更多关于HarmonyOS鸿蒙Next中如何在TaskPool和Woker获取上下文Context的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
TaskPool 和 Worker 线程中直接获取组件级上下文存在限制,试试通过以下方案传递上下文行不行:
// 主线程代码(如UIAbility)
import { taskpool } from '@kit.ArkTS';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { sendableContextManager } from '@kit.AbilityKit';
@Concurrent
function subTask(sendableCtx: sendableContextManager.SendableContext) {
const context = sendableContextManager.convertToContext(sendableCtx); // 转换为Context
// 使用context操作数据库或资源文件
}
async function executeTask() {
const sendableCtx = sendableContextManager.convertToSendableContext(this.context);
const task = new taskpool.Task(subTask, sendableCtx);
await taskpool.execute(task);
}
在HarmonyOS Next中,TaskPool和Worker不支持直接获取Context。TaskPool适用于轻量级任务并行,Worker用于后台任务。若需使用Context相关功能,可通过以下方式:
- 在主线程提前获取Context并序列化后传给TaskPool/Worker
- 使用全局Context(ApplicationContext)并确保线程安全
- 对于UI相关操作,通过postTask回传主线程处理
注意:跨线程传递Context需谨慎处理内存泄漏问题。
在HarmonyOS Next中,Worker和TaskPool线程确实无法直接获取组件级Context,但可以通过以下方式获取应用级Context:
- 主线程传递Context:
- 在主线程中获取应用级Context
- 通过Worker或TaskPool的postMessage方法将Context传递给子线程
- 使用getHostContext接口:
- 在子线程中通过getHostContext()获取Host进程的Context
- 注意这只能获取应用级Context,不是组件级的
示例代码:
// 主线程传递Context
const worker = new worker.ThreadWorker("entry/ets/workers/worker.ts");
worker.postMessage(getContext(this));
// Worker线程接收
workerPort.onmessage = (e) => {
const context = e.data; // 获取到的应用级Context
}
// 使用getHostContext
const context = getHostContext();
注意:
- 传递的Context是应用级的,不是组件级的
- 在TaskPool中同样适用这个方案
- 组件级Context建议在主线程处理完再传递结果