📑往期推文全新看点(文中附带最新·鸿蒙全栈学习笔记)

✒️ 鸿蒙(HarmonyOS)北向开发知识点记录~

✒️ 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~

✒️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?

✒️ 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~

✒️ 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?

✒️ 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?

✒️ 记录一场鸿蒙开发岗位面试经历~

✒️ 持续更新中……


使用TaskPool执行独立的耗时任务

对于一个独立运行的耗时任务,只需要在任务执行完毕后将结果返回给宿主线程,没有上下文依赖,可以通过以下方式实现。

下面以图片加载为例进行说明。

1.实现子线程需要执行的任务。

// IconItemSource.ets
export class IconItemSource {
  image: string | Resource = '';
  text: string | Resource = '';

  constructor(image: string | Resource = '', text: string | Resource = '') {
    this.image = image;
    this.text = text;
  }
}

// IndependentTask.ets
import { IconItemSource } from './IconItemSource';
 
// 在Task中执行的方法,需要添加@Concurrent注解,否则无法正常调用。
@Concurrent
export function loadPicture(count: number): IconItemSource[] {
  let iconItemSourceList: IconItemSource[] = [];
  // 遍历添加6*count个IconItem的数据
  for (let index = 0; index < count; index++) {
    const numStart: number = index * 6;
    // 此处循环使用6张图片资源
    iconItemSourceList.push(new IconItemSource('$media:startIcon', `item${numStart + 1}`));
    iconItemSourceList.push(new IconItemSource('$media:background', `item${numStart + 2}`));
    iconItemSourceList.push(new IconItemSource('$media:foreground', `item${numStart + 3}`));
    iconItemSourceList.push(new IconItemSource('$media:startIcon', `item${numStart + 4}`));
    iconItemSourceList.push(new IconItemSource('$media:background', `item${numStart + 5}`));
    iconItemSourceList.push(new IconItemSource('$media:foreground', `item${numStart + 6}`));
  }
  return iconItemSourceList;
}

2.通过TaskPool中的execute方法执行上述任务,即加载图片。

// Index.ets
import { taskpool } from '@kit.ArkTS';
import { IconItemSource } from './IconItemSource';
import { loadPicture } from './IndependentTask';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            let iconItemSourceList: IconItemSource[] = [];
            // 创建Task
            let lodePictureTask: taskpool.Task = new taskpool.Task(loadPicture, 30);
            // 执行Task,并返回结果
            taskpool.execute(lodePictureTask).then((res: object) => {
              // loadPicture方法的执行结果
              iconItemSourceList = res as IconItemSource[];
            })
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

使用TaskPool执行多个耗时任务

如果有多个任务同时执行,由于任务的复杂度不同,执行时间会不一样,返回数据的时间也是不可控的。如果宿主线程需要所有任务执行完毕的数据,那么可以通过下面这种方式实现。

除此以外,如果需要处理的数据量较大(比如一个列表中有10000条数据),把这些数据都放在一个Task中处理也是比较耗时的。那么就可以将原始数据拆分成多个列表,并将每个子列表分配给一个独立的Task进行执行,并且等待全部执行完毕后拼成完整的数据,这样可以节省处理时间,提升用户体验。

下面以多个任务进行图片加载为例进行说明。

1.实现子线程需要执行的任务。

// IconItemSource.ets
export class IconItemSource {
  image: string | Resource = '';
  text: string | Resource = '';

  constructor(image: string | Resource = '', text: string | Resource = '') {
    this.image = image;
    this.text = text;
  }
}

// IndependentTask.ets
import { IconItemSource } from './IconItemSource';
 
// 在Task中执行的方法,需要添加@Concurrent注解,否则无法正常调用。
@Concurrent
export function loadPicture(count: number): IconItemSource[] {
  let iconItemSourceList: IconItemSource[] = [];
  // 遍历添加6*count个IconItem的数据
  for (let index = 0; index < count; index++) {
    const numStart: number = index * 6;
    // 此处循环使用6张图片资源
    iconItemSourceList.push(new IconItemSource('$media:startIcon', `item${numStart + 1}`));
    iconItemSourceList.push(new IconItemSource('$media:background', `item${numStart + 2}`));
    iconItemSourceList.push(new IconItemSource('$media:foreground', `item${numStart + 3}`));
    iconItemSourceList.push(new IconItemSource('$media:startIcon', `item${numStart + 4}`));
    iconItemSourceList.push(new IconItemSource('$media:background', `item${numStart + 5}`));
    iconItemSourceList.push(new IconItemSource('$media:foreground', `item${numStart + 6}`));

  }
  return iconItemSourceList;
}

2.将需要执行的Task放到了一个TaskGroup里面,当TaskGroup中所有的Task都执行完毕后,会把每个Task运行的结果都放在一个数组中返回到宿主线程,而不是每执行完一个Task就返回一次,这样就可以在返回的数据里拿到所有的Task执行结果,方便宿主线程使用。

// MultiTask.ets
import { taskpool } from '@kit.ArkTS';
import { IconItemSource } from './IconItemSource';
import { loadPicture } from './IndependentTask';
 
let iconItemSourceList: IconItemSource[][];
 
let taskGroup: taskpool.TaskGroup = new taskpool.TaskGroup();
taskGroup.addTask(new taskpool.Task(loadPicture, 30));
taskGroup.addTask(new taskpool.Task(loadPicture, 20));
taskGroup.addTask(new taskpool.Task(loadPicture, 10));
taskpool.execute(taskGroup).then((ret: object) => {
  let tmpLength = (ret as IconItemSource[][]).length
  for (let i = 0; i < tmpLength; i++) {
    for (let j = 0; j < ret[i].length; j++) {
      iconItemSourceList.push(ret[i][j]);
    }
  }
})

TaskPool任务与宿主线程通信

如果一个Task,不仅需要返回最后的执行结果,而且需要定时通知宿主线程状态、数据的变化,或者需要分段返回数量级较大的数据(比如从数据库中读取大量数据),可以通过下面这种方式实现。

下面以多个图片加载任务结果实时返回为例进行说明。

1.首先,实现一个方法,用来接收Task发送的消息。

// TaskSendDataUsage.ets
function notice(data: number): void {
  console.info("子线程任务已执行完,共加载图片: ", data);
}

2.然后,在Task需要执行的任务中,添加sendData()接口将消息发送给宿主线程。

// IconItemSource.ets
export class IconItemSource {
  image: string | Resource = '';
  text: string | Resource = '';

  constructor(image: string | Resource = '', text: string | Resource = '') {
    this.image = image;
    this.text = text;
  }
}

// TaskSendDataUsage.ets
import { taskpool } from '@kit.ArkTS';
import { IconItemSource } from './IconItemSource';

// 通过Task的sendData方法,即时通知宿主线程信息
@Concurrent
export function loadPictureSendData(count: number): IconItemSource[] {
  let iconItemSourceList: IconItemSource[] = [];
  // 遍历添加6*count个IconItem的数据
  for (let index = 0; index < count; index++) {
    const numStart: number = index * 6;
    // 此处循环使用6张图片资源
    iconItemSourceList.push(new IconItemSource('$media:startIcon', `item${numStart + 1}`));
    iconItemSourceList.push(new IconItemSource('$media:background', `item${numStart + 2}`));
    iconItemSourceList.push(new IconItemSource('$media:foreground', `item${numStart + 3}`));
    iconItemSourceList.push(new IconItemSource('$media:startIcon', `item${numStart + 4}`));
    iconItemSourceList.push(new IconItemSource('$media:background', `item${numStart + 5}`));
    iconItemSourceList.push(new IconItemSource('$media:foreground', `item${numStart + 6}`));

    taskpool.Task.sendData(iconItemSourceList.length);
  }
  return iconItemSourceList;
}

3.最后,在宿主线程通过onReceiveData()接口接收消息。

这样宿主线程就可以通过notice()接口接收到Task发送的数据。

// TaskSendDataUsage.ets
@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            let iconItemSourceList: IconItemSource[];
            let lodePictureTask: taskpool.Task = new taskpool.Task(loadPictureSendData, 30);
            // 设置notice方法接收Task发送的消息
            lodePictureTask.onReceiveData(notice);
            taskpool.execute(lodePictureTask).then((res: object) => {
              iconItemSourceList = res as IconItemSource[];
            })
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

Worker和宿主线程的即时消息通信

在ArkTS中,Worker相对于Taskpool存在一定的差异性,有数量限制但是可以长时间存在。一个 Worker 中可能会执行多个不同的任务,每个任务执行的时长或者返回的结果可能都不相同,宿主线程需要根据情况调用Worker中的不同方法,Worker则需要及时地将结果返回给宿主线程。

下面以Worker响应"hello world"请求为例进行说明。

  1. 首先,创建一个执行多个任务Worker。
// Worker.ets
import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';

const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
// Worker接收宿主线程的消息,做相应的处理
workerPort.onmessage = (e: MessageEvents): void => {
  if (e.data === 'hello world') {
    workerPort.postMessage('success');
  }
}
  1. 这里的宿主线程为UI主线程,在宿主线程中创建这个Worker的对象,在点击Button的时候调用postmessage向Worker发送消息,通过Worker的onmessage方法接收Worker返回的数据。
// Index.ets
import { worker } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';

function promiseCase() {
  let p: Promise<void> = new Promise<void>((resolve: Function, reject: Function) => {
    setTimeout(() => {
      resolve(1);
    }, 100)
  }).then(undefined, (error: BusinessError) => {
  })
  return p;
}

async function postMessageTest() {
  let ss = new worker.ThreadWorker("entry/ets/workers/Worker.ets");
  let res = undefined;
  let flag = false;
  let isTerminate = false;
  ss.onexit = () => {
    isTerminate = true;
  }
  // 接收Worker线程发送的消息
  ss.onmessage = (e) => {
    res = e.data;
    flag = true;
    console.info("worker:: res is  " + res);
  }
  // 给Worker线程发送消息
  ss.postMessage("hello world");
  while (!flag) {
    await promiseCase();
  }

  ss.terminate();
  while (!isTerminate) {
    await promiseCase();
  }
}

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            postMessageTest();
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

在上文这段示例代码中,Worker接收来自宿主线程的消息,并做了相应处理后把结果发回给宿主线程。这样就可以实现宿主线程和Worker间的即时通信,方便宿主线程使用Worker的运行结果。

Worker同步调用宿主线程的接口

如果一个接口在主线程中已经实现了,Worker需要调用该接口,那么可以使用下面这种方式实现。

下面以Worker同步调用宿主线程接口为例进行说明。

首先,在宿主线程实现需要调用的接口,并且创建Worker对象,在Worker上注册需要调用的接口。

// IconItemSource.ets
export class IconItemSource {
  image: string | Resource = '';
  text: string | Resource = '';

  constructor(image: string | Resource = '', text: string | Resource = '') {
    this.image = image;
    this.text = text;
  }
}

// WorkerCallGlobalUsage.ets
import worker from '@ohos.worker';
import { IconItemSource } from './IconItemSource';

// 创建Worker对象
const workerInstance: worker.ThreadWorker = new worker.ThreadWorker("entry/ets/pages/workers/Worker.ts");

class PicData {
  public iconItemSourceList: IconItemSource[] = [];

  public setUp(): string {
    for (let index = 0; index < 20; index++) {
      const numStart: number = index * 6;
      // 此处循环使用6张图片资源
      this.iconItemSourceList.push(new IconItemSource('$media:startIcon', `item${numStart + 1}`));
      this.iconItemSourceList.push(new IconItemSource('$media:background', `item${numStart + 2}`));
      this.iconItemSourceList.push(new IconItemSource('$media:foreground', `item${numStart + 3}`));
      this.iconItemSourceList.push(new IconItemSource('$media:startIcon', `item${numStart + 4}`));
      this.iconItemSourceList.push(new IconItemSource('$media:background', `item${numStart + 5}`));
      this.iconItemSourceList.push(new IconItemSource('$media:foreground', `item${numStart + 6}`));

    }
    return "setUpIconItemSourceList success!";
  }
}

let picData = new PicData();
// 在Worker上注册需要调用的对象
workerInstance.registerGlobalCallObject("picData", picData);
workerInstance.postMessage("run setUp in picData");

2.然后,在Worker中通过callGlobalCallObjectMethod接口就可以调用宿主线程中的setUp()方法了。

// Worker.ets
import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
try {
  // 调用方法无入参
  let res: string = workerPort.callGlobalCallObjectMethod("picData", "setUp", 0) as string;
  console.error("worker: ", res);
} catch (error) {
  // 异常处理
  console.error("worker: error code is " + error.code + " error message is " + error.message);
}
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐