前言

1、并发的理解

  • 单线程模式:只有1位咖啡师,既要收银又要做咖啡
  • 并发模式:收银员负责点单,咖啡师做咖啡,清洁员打扫

2、为什么需要并发?

场景 问题 并发解决方案
加载大图 界面卡顿 后台线程加载
网络请求 等待时界面无响应 异步处理
复杂计算 应用“假死” 多线程并行计算
数据同步 用户无法操作 后台任务处理

目的:让应用保持流畅响应,同时完成复杂任务

一、并发概述

并发是指在同一时间内,存在多个任务同时执行的情况

  • 对于多核设备,这些任务可能同时在不同的CPU上并发执行
  • 对于单核设备,多个并发任务不会在同一时刻并行执行,但是CPU会在某个任务休眠或I/O操作等状态下切换任务,调度执行其他任务,提升CPU的资源利用率

1、异步并发

1.1 异步并发

是指异步代码在执行到一定程度后会倍暂停,以便在未来某个时间点继续执行。
例:定时器到时,网络请求返回时这类不涉及大量计算的IO密集型任务

1.2 多线程并发

允许在同一时间段内同时执行多端代码

  • TaskPool像一个公共的“段任务的线程池”,无状态,由系统自动调用,适合处理消耗时间比较短,可独立运行的任务
  • Worker是一个独立的长任务线程,有状态,并且支持与主线程进行通讯,适合处理需要持续占有资源的复杂计算任务

1.3 总结

异步并发解决的是等待期间不堵塞的问题
多线程并发解决的是计算本身耗时的问题
在这里插入图片描述

二、异步并发(Promise&async/await)

1、概述

Promise和async/await提供异步并发能力,是标准的JS异步语法。异步代码会被挂起并在之后继续执行,同一时间只有一段代码执行,适用于单词I/O任务的场景开发。无需另外启动线程执行
例:一次网络请求、一次文件读写等操作。
在这里插入图片描述

2、核心特点

非阻塞执行:主线程不被阻塞
事件驱动:操作完成触发回调
单线程内:所有代码在主线程执行
顺序不可控:先发起的操作不一定先完成

3、Promise三种状态

Pending(等待中)

  • 初始状态
  • 操作尚未完成
  • 如:外卖已下单但商家未准备好

Fulfilled(已完成)

  • 操作成功完成
  • 有最终结果值
  • 如:外卖已送达

Rejected(已拒绝)

  • 操作失败
  • 有失败原因
  • 如:外卖订单被取消
    在这里插入图片描述

4、Promise基础语法

const myPromise = new Promise((resolve, reject) => {
  // 这里是异步操作

  // 操作成功时的调用
  resolve("成功的结果")
  
  // 操作失败时调用
  reject("失败的原因")
})

参数说明:
resolve:将状态改为Fulfilled
reject:将状态改为Rejected

5、案例

使用Promise获取结果

// 1、创建模拟下载的Promise
const downloadFile = new promise((resolve, reject) => {
  console.log('开始下载文件。。。')

  // 模拟2s下载
  setTimeout(() => {
    const success = Math.random > 0.3

    if (success) {
      resolve('文件内容数据')
    } else {
      reject('网络错误,下载失败')
    }
  }, 2000)
})

// 2、使用Promise
downloadFile
  .then((data) => {
    // 成功时执行
    console.log('下载成功:', data)
  })
  .catch((error) => {
    // 失败时执行
    console.log('下载失败:', err)
  })
  .finally(() => {
    // 无论失败或成功都执行
    console.log('下载流程结束')
  })

6、Promise核心方法总结

方法 作用 使用场景
then() 处理成功状态 接收成功结果
catch() 处理失败状态 错误捕获任务
finally() 最终执行 清理资源
resolve() 创建成功Promise 包装已知值
reject() 创建失败Promise 包装错误

7、async/await

async/await是一种用于处理异步操作的Promise语法糖,使得编写异步代码变得更加简单和易读。通过使用async关键字声明一个函数为异步函数,并使用await关键字等待Promise的解析(完成或拒绝),以同步的方式编写异步操作的代码

想象在餐厅点餐:

  • 使用Promise时:你拿到取餐号(Promise),然后时不时检查是否叫你的号(.then())
  • 使用async/await:你拿到一个叫号器(async函数),但餐准备好的时候叫号器会发生振动(await),你不需要主动去检查

简单说:

  • async:声明一个函数是异步的
  • await:等待一个Promise完成(成功或失败)

8、代码对比

使用Promise链式调用:
在这里插入图片描述
使用async/await:
在这里插入图片描述
可以看到,async/await版本:结构更清晰,像同步代码一样从上到下执行;错误处理更自然,使用try/catch即可

9、具体实现

9.1声明async函数

在函数前面加上async关键字,表示这个函数内部可以使用await
在这里插入图片描述

9.2 使用await等待Promise

在Promise前面加上await,代码会暂停在这里直到Promise完成(成功或失败)

  • 如果Promise成功:await返回Promise的解决值
  • 如果Promise失败:await抛出拒绝原因(可以使用try/catch捕获)
    在这里插入图片描述

9.3 错误处理

使用try/catch捕获await可能抛出的错误
在这里插入图片描述

核心代码

@Entry
@Component
struct makeBreakfast {
  build() {
    Column({ space: 10 }) {
      Text('早餐制作演示')
        .fontSize(20)
      Button('使用Promise链式调用')
        .backgroundColor('#007dff')
        .fontColor('#ffffff')
        .onClick(() => {
          this.makeBreakfastWithPromise()
        })
      Button('使用async/await')
        .backgroundColor('#00b42a')
        .fontColor('#ffffff')
        .onClick(() => {
          this.makeBreakfastWithAsyncAwait()
        })
    }.width('100%')
    .height('100%')
    .padding(20)
  }

  // ========== 基础异步操作(两种方式共用) ==========
  // 烧开水(返回Promise)
  private boilWater(): Promise<string> {
    return new Promise((resolve) => {
      console.log('开始烧开水')
      // 模拟2s烧开水
      setTimeout(() => {
        // 返回“沸水”
        resolve("沸水")
      }, 2000)
    })
  }

  // 煮面条(接收水作为参数,返回Promise)
  private cookNoodles(water: string): Promise<string> {
    return new Promise((resolve) => {
      console.log('用' + water + "煮面条。。。")
      // 模拟1.5s煮面条
      setTimeout(() => {
        // 返回“劲道面条”
        resolve("劲道面条")
      }, 1500)
    })
  }

  // 切葱花(返回Promise)
  private chopScallion(): Promise<string> {
    return new Promise((resolve) => {
      console.log("开始切葱花。。。")
      // 模拟1s切葱花
      setTimeout(() => {
        // 返回“香喷喷的葱花”
        resolve("香喷喷的葱花")
      }, 1000)
    })
  }

  // ========== 方法1:Promise链式调用 ==========
  private async makeBreakfastWithPromise() {
    console.log("=== 开始使用Promise做早餐 ===");
    // 1. 等待水烧开
    this.boilWater()
    // 2. 煮面同时切葱花(并行执行)
      .then((water) => {
        return this.cookNoodles(water)
      })
      .then((noodles) => {
        // 用Promise.all同时处理切葱花和等待面条(此处面条已完成,主要并行切葱花)
        return Promise.all([Promise.resolve(noodles), this.chopScallion()])
      })
      // 3. 所有步骤完成后输出结果
      .then((res) => {
        console.log('早餐完成:' + res[0] + "+" + res[1])
      })

      .catch((error: Error) => {
        console.error('做饭失败' + error)
      })
  }

  // ========== 方法2:async/await ==========
  private async makeBreakfastWithAsyncAwait() {
    console.log("=== 开始使用async/await做早餐 ===");
    try {
      // 等待水烧开
      const water = await this.boilWater()
      // 用水煮面(等待面条煮熟)
      const noodles = await this.cookNoodles(water)
      // 等待切葱花
      const scallion = await this.chopScallion()
      console.log('早餐完成:' + noodles + "+" + scallion)
    } catch (error) {
      console.error('做饭失败' + error)
    }
  }
}

三、多线程并发

1、多线程概述

并发模型时用来实现不同应用场景中并发任务的编程模型,常见的并发模型和基于消息通信的并发模型
Actor并发模型作为基于消息通信并发模型的典型代表,不需要开发者去面对锁带来的一系列复杂偶发的问题,同时并发度也相对较高,因此得到了广泛的支持和代表
当前,ArkTS提供了TaskPoolWorker两种并发能力,TaskPool和Worker都基于Actor并发模型实现

2、多线程优势

充分利用多核CPU
避免主线程阻塞
提升应用响应速度
实现真正并行处理

3、TaskPool

3.1 TaskPool引入

想象你去银行办事:

  • 传统方式:每件事都要开一个新窗口(线程)
  • TaskPool方式:所有顾客共享一个服务窗口池(线程池)

在编程中,TaskPool是鸿蒙系统提供的线程池管理工具

  • 系统维护一组可重用的线程
  • 自动分配任务给空闲线程
  • 适合处理大量短期任务

3.2 TaskPool的优势

TaskPool的优势:

  • 自动复用线程(减少开销)
  • 系统级负载均衡
  • 内置任务队列管理
  • 默认超时保护(3分钟)

3.3 TaskPool核心概念

任务(Task)

  • 要执行的工作单元
  • 使用@Concurrent装饰器标记函数

线程池

  • 系统维护的线程集合
  • 无需手动创建或销毁

任务队列

  • 待处理任务的等待区
  • 自动先进先出调度
    在这里插入图片描述

3.4 TaskPool使用

步骤①:定义并发函数
在这里插入图片描述

步骤②:创建任务并提交
在这里插入图片描述
步骤③:批量任务处理
在这里插入图片描述

3.5 TaskPool核心代码

import taskpool from '@ohos.taskpool'

// ========== 定义并发函数 ==========
@Concurrent
function calculateSum(start: number, end: number): number {
  // 模拟耗时计算(1-end的累加,模拟密集型任务)
  let sum = 0
  for (let i = start; i <= end; i++) {
    sum += 1
  }
  console.log('任务完成' + start + "-" + end + "的和为" + sum)
  return sum
}


@Entry
@Component
struct TaskPool {
  @State result: string = '点击按钮执行并发任务'
  @State calculateSum: number[] = []

  // ========== 执行单个并发任务 ==========
  private async runBatchTask() {
    try {
      this.result = "任务执行中..."
      // 1、创建Task对象(绑定并发函数和参数)
      const task = new taskpool.Task(calculateSum, 1, 1000000)
      // 2、提交到TaskPool执行(异步等待结果)
      const sum = await taskpool.execute(task)
      // 3、处理结果
      this.result = `单个任务完成:\n1-100的和为:${sum}`
    } catch (error) {
      this.result = `单个任务失败:\n${error}`
      console.log(`单个任务失败:${error}`)
    }

  }

  // ========== 执行多个并发任务 ==========
  private async runBatchTasks() {
    try {
      this.result = '批量任务执行中...'
      // 1、创建TaskGroup
      const taskGroup = new taskpool.TaskGroup()
      // 2、向TaskGroup中添加多个Task(替代原Task数组)
      // 任务1
      taskGroup.addTask(new taskpool.Task(calculateSum, 1, 300000))
      // 任务2
      taskGroup.addTask(new taskpool.Task(calculateSum, 300001, 600000))
      // 任务3
      taskGroup.addTask(new taskpool.Task(calculateSum, 600001, 1000000))
      // 3、提交TaskGroup执行
      const results = await taskpool.execute(taskGroup) as number[]
      // 4、汇总结果(results是按addTask顺序返回的结果数组)
      const totalSum = results.reduce((a, b) => a + b, 0)
      this.result = `批量任务完成:\n3个任务并行执行\n个任务结果:${results}\n总和=${totalSum}`
    } catch (error) {
      this.result = `批量任务失败:${error}`
      console.log(`批量任务失败:${error}`)
    }
  }

  build() {
    Column({ space: 20 }) {
      Text('TaskPool 核心用法演示')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
      // 执行单个并发任务
      Button('执行单个并发任务')
        .backgroundColor('#0070ef')
        .fontColor('#ffffff')
        .padding(12)
        .borderRadius(8)
        .onClick(() => {
          this.runBatchTask()
        })
      // 执行多个并发任务(体现并行)
      Button('执行多个并发任务')
        .backgroundColor('#00b42a')
        .fontColor('#ffffff')
        .padding(12)
        .borderRadius(8)
        .onClick(() => {
          this.runBatchTasks()
        })
      Text(this.result)
        .fontSize(16)
        .textAlign(TextAlign.Center)
        .width('100%')
    }.width('100%')
    .height('100%')
    .padding(20)
    .justifyContent(FlexAlign.Center)
  }
}

4、Worker

4.1 Worker引入

想象你开了一家餐厅:

  • 主线程像餐厅经理:负责接待客人、处理订单
  • Worker像后厨厨师:负责烹饪美食,不被打扰

在编程中,Worker是鸿蒙提供的独立后台线程

  • 拥有完全独立的内存空间
  • 适合执行长时间任务
  • 不阻塞主线程运行
  • 通过消息机制通信

4.2 Worker特性

  • 独立线程不受时间限制
    即,可一直执行
  • 可保持任务中间状态
    即,适合需要多个步骤,并且记住之气那步骤结果的长任务
  • 与主线程完全隔离
    即,避免复杂的锁问题和数据竞争,更安全

4.3 Worker核心概念

通信机制

  • postMessage:发送消息
  • onmessage:接收消息
  • 数据需要序列化传递(不能传函数)
    在这里插入图片描述

4.4 Worker使用

步骤①:创建Worker
在这里插入图片描述
步骤②:发送消息
在这里插入图片描述
步骤③:Worker接收信息,并回传宿主进程
在这里插入图片描述
步骤④:宿主进程接收结果在这里插入图片描述

4.5 Worker核心代码

[ src/main/ets/pages/Index.ets ] :

import worker, { ErrorEvent, MessageEvents } from '@ohos.worker';

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

  build() {
    RelativeContainer() {
      Text(this.message)
        .id('WorkerHelloWorld')
        .fontSize($r('app.float.page_text_font_size'))
        .fontWeight(FontWeight.Bold)
        .alignRules({
          center: { anchor: '__container__', align: VerticalAlign.Center },
          middle: { anchor: '__container__', align: HorizontalAlign.Center }
        })
        .onClick(() => {
          // 创建Worker
          let workerInstance: worker.ThreadWorker = new worker.ThreadWorker("entry/ets/workers/Worker.ets")

          // 注册onmessage回调,捕获宿主线程接收到来自其创建的Worker通过workerPort.onmessage接口发送的消息
          workerInstance.onmessage = (e: MessageEvents) => {
            let data: string = e.data
            console.info("workerInstance onmessage is:", data)
          }
          // 注册onAllErrors回调,捕获Worker线程的onmessage回调、timer回调以及文件执行等流程产生的全局异常。该回调在宿主线程执行
          workerInstance.onAllErrors = (err: ErrorEvent) => {
            console.error("workerInstance onAllErrors message is:", err.message)
          }
          // 注册onmessageerror回调,当Worker对象接收到无法序列化的消息时被调用,在宿主线程执行
          workerInstance.onmessageerror = () => {
            console.error("workerInstance onmessageerror")
          }
          // 注册onexit回调,当Worker销毁时被调用,在宿主线程执行
          workerInstance.onexit = (e: number) => {
            // Worker正常退出时,code为0;异常退出时,code为1
            console.info("workerInstance onexit code is:", e)
          }
          // 发送消息给Worker线程
          try {
            workerInstance.postMessage("1")
          } catch (error) {
            // TODO: Implement error handling.
          }
        })
    }
    .height('100%')
    .width('100%')
  }
}

[ src/main/ets/workers/Worker.ets ] :

import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';

const workerPort: ThreadWorkerGlobalScope = worker.workerPort;

// 注册onmessage回调,当Worker线程收到来自其宿主线程通过postMessage接口发送的消息时被调用,在Worker线程执行
workerPort.onmessage = (e: MessageEvents) => {
  let data: string = e.data
  console.info("workerPort onmessage is:", data)

  // 向宿主线程发送消息
  try {
    workerPort.postMessage('2')
  } catch (error) {
    // TODO: Implement error handling. 额外
  }
}

// 注册onmessageerror回调,当Worker对象接收一条无法被序列化的消息时被调用,在Worker线程执行
workerPort.onmessageerror = (event: MessageEvents) => {
  console.error("workerPort onmessageerror")
};

// 注册onerror回调,捕获Worker在执行过程中发生的异常,在Worker线程执行
workerPort.onerror = (event: ErrorEvent) => {
  console.error("workerPort onerror err is:",event.message)
};

四、并发线程间通信

1、什么是线程间通信?

想象两个办公室的同时协作:

  • 主线程:前台接待员(负责与用户交互)
  • Worker线程:仓库管理员(负责后台数据处理)

他们需要协作完成订单处理:

  • 接待员接收客户订单(主线程)
  • 将订单传给仓库管理员(线程通信)
  • 仓库准备货物(Worker处理)
  • 仓库返回准备状态(线程通信)
  • 接待员通知客户取货(主线程)

2、为什么需要线程通信?

当应用使用多线程时:

  • 主线程负责UI和用户交互
  • 后台线程处理耗时任务

他们需要交换:

  • 任务指定(“开始处理这个”)
  • 处理进度(“已完成50%”)
  • 最终结果(“已处理完成,这是结果”)
  • 错误信息(“处理失败”)

没有通信机制,线程就像孤岛,无法协作完成任务!

3、鸿蒙线程通信核心方式

3.1 消息传递(postMessage)

请添加图片描述

3.2 共享内存(SharedArrayBuffer)

在这里插入图片描述

3.3 事件订阅发布

在这里插入图片描述

五、应用多线程开发

多线程设计原则
黄金法则:

  • UI线程只做UI更新
  • 耗时操作玩不移出主线程
  • 根据任务特性选择并发方式
    TaskPool:短期无任务状态(<3分钟)
    Worker:长期有状态任务
    在这里插入图片描述
    在这里插入图片描述

六、小结

并发概述:Actor模型实现线程隔离,TaskPool与Worker分治短长任务
异步并发:Promise+async/await化解回调地狱,单线程堵塞
多线程并发:TaskPool轻量化调度短期任务,Worker独立攻克长时工程
线程通信:postMessage消息传递,SharedArrayBuffer共享内存
应用实战:UI线程零阻塞设计,资源管控

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐