#2023盲盒+码# Taskpool概念及使用 原创

zhushangyuan_
发布于 2023-9-17 10:32
浏览
0收藏

【本文正在参加 2023「盲盒」+码有奖征文活动】,活动链接 https://ost.51cto.com/posts/25284

Taskpool概念及使用

基本概念

#2023盲盒+码#  Taskpool概念及使用-开源基础软件社区#2023盲盒+码#  Taskpool概念及使用-开源基础软件社区

TaskPool支持开发者在主线程封装任务抛给任务队列,系统选择合适的工作线程,进行任务的分发及执行,再将结果返回给主线程。接口直观易用,支持任务的执行、取消,以及指定优先级的能力,同时通过系统统一线程管理,结合动态调度及负载均衡算法,可以节约系统资源。系统默认会启动一个任务工作线程,当任务较多时会扩容,工作线程数量上限跟当前设备的物理核数相关,为max(3, 物理核数-1)个,长时间没有任务分发时会缩容,减少工作线程数量。

使用场景

由于TaskPool的工作线程会绑定系统的调度优先级,并且支持负载均衡(自动扩缩容),而Worker需要开发者自行创建,存在创建耗时以及不支持设置调度优先级,故在性能方面使用TaskPool会优于Worker,因此大多数场景推荐使用TaskPool。注意,TaskPool偏向独立任务(线程级)维度,超长任务(大于3分钟)会被系统自动回收。

推荐使用TaskPool的场景如下:

需要设置优先级的任务。例如图库直方图绘制场景,后台计算的直方图数据会用于前台界面的显示,影响用户体验,需要高优先级处理,需要使用TaskPool。

需要频繁取消的任务。例如图库大图浏览场景,为提升体验,会同时缓存当前图片左右侧各2张图片,往一侧滑动跳到下一张图片时,要取消另一侧的一个缓存任务,需要使用TaskPool。

大量或者调度点较分散的任务。例如大型应用的多个模块包含多个耗时任务,不方便使用8个Worker去做负载管理,推荐采用TaskPool。

下面我们将使用按需加载朋友信息的场景,来演示如何使用TaskPool。

这是一个类朋友圈的场景,使用LazyForEach实现了列表项的加载。我们设计了分页加载的功能,每次从网络中加载10条数据;当列表缓存加载满10条后,会增量加载10条网络数据。由于网络加载会随着用户的滑动操作频繁进行,因此我们把网络加载逻辑放到子线程中,避免影响主线程的渲染绘制性能。LazyForEach的实现逻辑如下所示:

LazyForEach(this.momentData, (msg: FriendMoment) => {
  ListItem() {
    OneMoment({ moment: msg })
  }
}, (msg: FriendMoment) => msg.id)

首先在页面中使用LazyForEach组件,传入需要渲染的momentData数据,设置cachedCount缓存数为3,系统会首先渲染进入页面时需要渲染的组件,并同时缓存即将渲染的3条数据。

  1. cachedCount设置为3是因为在实际测试中为了保证用户滑动时不会因为网络延迟造成滑到底部时数据还未加载,所以选择让懒加载缓存3条数据。当用户滑动到倒数第3条数据时,实际上此时LazyForEach组件就已经缓存到最后一项了,那么我们就可以在此时调用方法获取下一页需要渲染的网络数据。
  2. LazyForEach组件在滑动页面的过程中,需要调用getData方法来渲染的每一项,传入的参数index就代表了此时是第几行数据,页面初次进入时,组件除了调用当前需要渲染的getData,还会因为设置了cachedCount的缘故而多调用3次getData。因此当页面滑动到倒数第三行时,getData就已经执行了十次,所以我们只需要判断当index等于所有数据项的最后一位时调用网络方法获取下一页数据即可。
  3. 因为分页获取网络数据会频繁使用网络资源,为了保证应用能在网络条件不好的情况下不阻塞,所以选择使用TaskPool任务池,将调用方法放进任务池中执行,为应用程序提供一个多线程的运行环境,降低整体资源的消耗、提高系统的整体性能。
class FriendMomentsData extends BasicDataSource<FriendMoment> {
  momentList: Array<FriendMoment> = []
  // 根据index获取具体项
  public getData(index: number): FriendMoment {
    // 设置判断条件
    if (index == this.momentList.length - 1) {
      //调用 getFriendMomentByTaskPool方法,传入的参数为获取数据的网络路径。
      getFriendMomentByTaskPool(friendMomentsJsonUrl[this.getJsonIndex % 3])
      // 每次调用完成后增加1,代表页数加一
      this.getJsonIndex++
    }
    return this.momentList[index];
  }
}
  1. index == this.momentList.length - 1 用来判断系统是否已经缓存到最后一项数据了,如果相等则需调用方法获取下一页数据。
  2. this.getJsonIndex % 3取余时因为目前模拟的数据只有三个json文件,为了实现循环获取所以选择取3的余数当做url数组的index。
  3. 每次调用后getJsonIndex加一,也代表了页数加一。
// 引入TaskPool模块
import taskpool from '@ohos.taskpool';
// 通过任务池获取朋友圈模拟数据   
async function getFriendMomentByTaskPool(jsonUrl) {
  // 创建task任务项,传入1.被调用的方法 2.方法所需参数 
  let task = new taskpool.Task(getWebData, jsonUrl);
  // 调用taskpool.execute执行任务
  let webData = await taskpool.execute(task, taskpool.Priority.MEDIUM) as string;
  // 将结果传入getFriendMomentObj方法中格式化成对象
  let friendMomentJsonStr = await getFriendMomentObj(webData['result']);
  // 获取页面组件中的momentData对象,其中是组件所需的username、image、video等数据
  let momentData = AppStorage.get<FriendMomentsData>('momentData')
  // 循环遍历对象并依次传入momentData
  for (let i = 0; i < friendMomentJsonStr.length; i++) {
    momentData.pushData(friendMomentJsonStr[i]);
  }
  // 将更新的momentData返回给页面组件
  AppStorage.setOrCreate('momentData', momentData)
}

TaskPool使用方法:

  1. 首先import引入TaskPool模块。
  2. new一个task对象,其中传入被调用的方法getWebData和方法需要的参数jsonUrl。getWebData方法需要一个string类型的url,返回一个http.response类型的对象。
  3. 之后使用taskpool.execute启动任务池,将待执行的函数放入taskpool内部任务队列等待,等待分发到工作线程执行。execute需要两个参数:1.创建的任务对象 2. 等待执行的任务组的优先级,默认值是Priority.MEDIUM。
  4. execute方法返回的是一个json字符串,此时需要把结果传入getFriendMomentObj方法格式化成页面需要的momentData对象。
  5. 之后将新获取的momentData通过AppStorage.setOrCreate传入页面组件中 。
    任务池官方文档@ohos.taskpool

注意:

  1. 目前使用execute方法返回的是一个promise对象,但是在then()中无法正常获取被执行函数返回的结果,因此这里选择使用赋值的方法来获取返回值。
  2. 因为后续需要将返回值继续格式化,所以需要在调用execute方法时使用await等待执行完成再继续执行下面的操作。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
收藏
回复
举报
回复
    相关推荐