HarmonyOS 鸿蒙Next中异步任务处理与UI线程通信

HarmonyOS 鸿蒙Next中异步任务处理与UI线程通信

如何优雅地处理耗时任务避免UI卡顿?

4 回复

使用多线程开发。典型的耗时任务包括CPU密集型任务、I/O密集型任务和同步任务,每种任务类型对应不同的业务场景。TaskPool和Worker均支持多线程并发,可根据业务场景选择合适的并发能力,具体请参考TaskPool和Worker适用场景对比

更多关于HarmonyOS 鸿蒙Next中异步任务处理与UI线程通信的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


使用描述

在应用开发中,我们不可避免地会遇到一些耗时操作,如网络请求、复杂计算、大文件读写或数据库查询。如果将这些操作直接在UI线程(主线程)中执行,将会导致界面卡顿甚至无响应(ANR),严重影响用户体验。不过鸿蒙OS为我们提供了强大的多线程能力,允许我们将耗时任务放到后台线程(Worker线程)中执行。当任务完成后,再通过安全的方式将结果传回UI线程进行更新。

实现思路

1、创建异步任务:

首先,我们需要定义一个执行耗时任务的函数。这个函数必须使用 @Concurrent 装饰器标记,表示它可以被并发执行。这个函数接收输入参数,执行耗时逻辑,最后返回结果。

2、使用 TaskPool 提交任务:

TaskPool 是鸿蒙OS提供的任务调度器,它会管理一个线程池,高效地执行我们提交的任务。

在UI组件中,我们通过 TaskPool.execute() 方法,将 @Concurrent 函数和一个包含参数的对象封装成一个任务提交出去。execute() 方法会立即返回一个 Promise 对象。

3、处理 Promise 结果:

Promise 对象代表了一个异步操作的最终完成。

我们可以使用 .then() 方法来注册一个回调,这个回调会在任务成功完成并被调度回主线程时执行。

在 .then() 的回调函数中,我们可以安全地更新UI状态,因为此时代码执行环境已经回到了UI线程。

使用场景

任何可能导致UI卡顿超过的操作,都可以考虑使用异步任务处理来进行使用体验的优化。比如网络请求:获取服务器数据、上传文件等; 图片/视频处理:图片压缩、格式转换、视频解码等; 复杂数据计算:大数据集的排序、过滤、统计分析等;文件I/O操作:读写大文件、遍历目录等。

实现效果

效果图

完整代码

simulateDownload

/**
 * 模拟下载的并发任务函数
 * 使用 [@Concurrent](/user/Concurrent) 装饰器,使其能在 TaskPool 中运行
 * @param targetSize 目标下载大小
 * @returns 返回最终的下载状态信息
 */
export interface GeneratedTypeLiteralInterface_1 {
  success: boolean;
  finalSize: number;
}

[@Concurrent](/user/Concurrent)
export function simulateDownload(targetSize: number): GeneratedTypeLiteralInterface_1 {
  console.info(`[TaskPool] Task started, target size: ${targetSize}`);
  let currentSize = 0;
  const step = 5; // 每次模拟下载5MB

  // 模拟耗时下载过程
  while (currentSize < targetSize) {
    // 模拟网络延迟
    let start = Date.now();
    while (Date.now() - start < 200) {} // 模拟200ms的网络耗时

    currentSize += step;
    console.info(`[TaskPool] Progress: ${currentSize}/${targetSize}`);
  }

  console.info(`[TaskPool] Task finished.`);
  return { success: true, finalSize: currentSize };
}

AsyncTaskPage

import { simulateDownload,GeneratedTypeLiteralInterface_1 } from './ConcurrentTask';
import taskpool from '@ohos.taskpool';

@Entry
@Component
struct AsyncTaskPage {
  @State downloadProgress: number = 0; // 下载进度 (0-100)
  @State isDownloading: boolean = false; // 是否正在下载
  @State statusMessage: string = '点击按钮开始下载'; // 状态信息

  build() {
    Column() {
      Text('鸿蒙OS 5.0 异步任务处理 Demo')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 50, bottom: 30 })

      // 进度条
      Progress({ value: this.downloadProgress, total: 100, type: ProgressType.Linear })
        .width('90%')
        .height(20)
        .color('#007DFF')
        .margin({ bottom: 10 })

      // 进度文本
      Text(`${this.downloadProgress}%`)
        .fontSize(18)
        .fontColor('#333333')
        .margin({ bottom: 30 })

      // 状态信息
      Text(this.statusMessage)
        .fontSize(16)
        .fontColor('#666666')
        .width('90%')
        .textAlign(TextAlign.Center)
        .margin({ bottom: 40 })

      Button(this.isDownloading ? '下载中...' : '开始模拟下载')
        .width('80%')
        .height(50)
        .fontSize(18)
        .enabled(!this.isDownloading) // 下载时禁用按钮
        .onClick(() => {
          this.startDownloadTask();
        })

    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .backgroundColor('#F1F3F5')
  }

  /**
   * 启动下载任务的方法
   */
  private startDownloadTask() {
    this.isDownloading = true;
    this.downloadProgress = 0;
    this.statusMessage = '任务已提交,正在后台执行...';

    // 1. 创建任务
    // 将 simulateDownload 函数和参数 100 (目标大小) 封装成一个 task
    let task = new taskpool.Task(simulateDownload, 100);

    // 2. 提交任务到 TaskPool
    taskpool.execute(task).then((result) => {
      // 3. 任务成功完成后的回调 (此回调在UI线程执行)
      console.info(`[UI] Task succeeded, result: ${JSON.stringify(result)}`);
      this.downloadProgress = 100;
      this.statusMessage = `下载完成!`;
    }).finally(() => {
      // 无论成功失败,都会执行
      this.isDownloading = false;
    });

    // 模拟UI线程的响应性
    let intervalId = setInterval(() => {
      if (this.downloadProgress < 99) {
        this.downloadProgress += 2;
      } else {
        clearInterval(intervalId);
      }
    }, 400);
  }
}

鸿蒙Next中异步任务通过TaskPool或Worker实现,避免阻塞UI线程。TaskPool适用于轻量级任务,Worker适合长时间运行任务。UI线程通信使用TaskPool的execute()或Worker的postMessage()方法传递数据,通过回调或事件机制更新UI。使用@Sendable装饰器标记可序列化数据,确保线程间安全传递。

在HarmonyOS Next中,处理耗时任务并避免UI卡顿的核心是使用异步并发能力,并通过TaskDispatcher将结果安全地派发回UI线程。

推荐使用TaskPoolworker_thread(Worker线程)来处理CPU密集型或I/O密集型任务。对于轻量级任务,TaskPool是更高效的选择,它基于线程池实现,能自动管理任务生命周期。对于需要长时间运行或状态维护的任务,应使用Worker线程。

关键步骤:

  1. 定义异步任务:将耗时操作封装在异步函数或Worker线程中。
  2. 使用TaskPool执行:通过taskpool.execute()提交任务,它返回一个Promise
  3. 返回UI线程更新:在Promisethen回调或使用async/await获取结果后,必须通过MainTaskDispatcher将UI更新操作派发到UI线程。

示例代码(使用TaskPool):

import taskpool from '@ohos.taskpool';
import { BusinessError } from '@ohos.base';
import common from '@ohos.app.ability.common';

// 1. 定义耗时任务函数,用@Concurrent装饰器标记
@Concurrent
function computeHeavyTask(param: number): number {
  // 模拟耗时计算
  let sum = 0;
  for (let i = 0; i <= param; i++) {
    sum += i;
  }
  return sum;
}

// 在UI上下文中(如EntryAbility或Page中)
let context = getContext(this) as common.UIAbilityContext;

// 2. 使用TaskPool执行异步任务
taskpool.execute(computeHeavyTask, 1000000).then((result: number) => {
  // 3. 获取结果后,通过UITaskDispatcher派发到UI线程更新组件
  context.uiTaskDispatcher.asyncDispatch(() => {
    // 在此安全地更新UI状态,例如:
    // this.myText = `Result: ${result}`;
    console.info(`UI updated with result: ${result}`);
  });
}).catch((err: BusinessError) => {
  console.error(`Task failed: ${err.code} ${err.message}`);
});

要点

  • UI线程(主线程)只负责轻量级任务和UI渲染,任何可能超过几毫秒的操作都应移至异步任务。
  • 只有通过UITaskDispatcher(即context.uiTaskDispatcher)派发的任务才能安全地操作UI组件。
  • 使用TaskPool时,传递给execute()的函数参数需支持序列化(如基本类型、普通对象),且函数本身需用@Concurrent装饰器标记。
  • 对于更复杂的场景(如持续通信、大量数据传输),建议使用Worker线程,并通过postMessage()onmessage事件进行线程间通信。

这种模式能有效保证UI流畅性,符合HarmonyOS Next的并发设计规范。

回到顶部