鸿蒙开发--异步并发
并发编程是现代应用开发的核心技术之一。本文系统介绍了并发的基本概念、应用场景及两种主流实现方式: 并发概述:通过咖啡店类比解释并发优势,说明并发可提升资源利用率,保持应用响应流畅。区分了多核设备的真正并行和单核设备的任务切换。 异步并发(Promise/async-await): 采用非阻塞执行模式处理I/O密集型任务 Promise提供三种状态管理(Pending/Fulfilled/Rejec
前言
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提供了TaskPool和Worker两种并发能力,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线程零阻塞设计,资源管控
更多推荐

所有评论(0)