HarmonyOS鸿蒙Next中如何在TaskPool和Woker获取上下文Context

HarmonyOS鸿蒙Next中如何在TaskPool和Woker获取上下文Context

Worker线程和TaskPool线程中无法直接获取到组件级的Context。可以通过主线程参数传递应用级Context,通过getHostContext接口获取Context上下文。

4 回复

开发者您好,可以参考以下方案实现多线程中传递上下文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


TaskPoolWorker 线程中直接获取组件级上下文存在限制,试试通过以下方案传递上下文行不行:

// 主线程代码(如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相关功能,可通过以下方式:

  1. 在主线程提前获取Context并序列化后传给TaskPool/Worker
  2. 使用全局Context(ApplicationContext)并确保线程安全
  3. 对于UI相关操作,通过postTask回传主线程处理

注意:跨线程传递Context需谨慎处理内存泄漏问题。

在HarmonyOS Next中,Worker和TaskPool线程确实无法直接获取组件级Context,但可以通过以下方式获取应用级Context:

  1. 主线程传递Context:
  • 在主线程中获取应用级Context
  • 通过Worker或TaskPool的postMessage方法将Context传递给子线程
  1. 使用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();

注意:

  1. 传递的Context是应用级的,不是组件级的
  2. 在TaskPool中同样适用这个方案
  3. 组件级Context建议在主线程处理完再传递结果
回到顶部