cover_image

鸿蒙开发中的并发处理

吕游 微鲤技术团队
2024年11月25日 08:20

并发

  • 并发是指在一个时间段内,多个事件、任务或操作同时进行或者交替进行的方式。

  • 在计算机科学中,特指多个任务或程序同时执行的能力。

  • 并发可以提升系统的吞吐量、响应速度和资源利用率,并能更好地处理多用户、多线程和分布式的场景。

  • 常见的并发模型有多线程、多进程、多任务、协程等。


 01

并发概述

为了提升应用的响应速度与帧率,避免耗时任务对主线程的影响,HarmonyOS提供了异步并发和多线程并发两种处理策略。

图片

HarmonyOS中的异步并发和多线程并发

图片     

02

异步并发

  • Promise和async/await提供异步并发能力,是标准的JS异步语法。

  • 异步代码会被挂起并在之后继续执行,同一时间只有一段代码执行,适用于单次I/O任务的场景开发,例如一次网络请求、一次文件读写等操作。无需另外启动线程执行。

  • 异步语法是一种编程语言的特性,允许程序在执行某些操作时不必等待其完成,而是可以继续执行其他操作。

1. Promise

  • Promise是一种用于处理异步操作的对象。它表示一个可能还未完成的操作,并提供了一系列方法来处理操作的结果或错误。

  • Promise对象有三种状态:pending(进行中)、fulfilled(已完成)和rejected(已失败)。当操作完成时,Promise对象将会从pending状态转变为fulfilled或rejected状态,并调用相应的回调函数。

  • 使用Promise可以更加方便地管理异步操作,并避免回调函数嵌套过多的问题。

  • Promise是一种用于处理异步操作的对象。它可以认为是一个代理,用来代表一个尚未完成但最终会完成的操作。

 Promise实例

 myAsyncFunction(): Promise<string> {  
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true; // 模拟提交成功
if (success) {
resolve('提交成功');
} else {
reject('提交失败');
}
}, 1000)
})
}
    import { BusinessError } from '@kit.BasicServicesKit';

this.myAsyncFunction().then((result: string) => {
console.log(result)
})
.catch((error: BusinessError) => {
console.log(error.message)
})
.finally(() => {
console.log("操作完成")
})

通过then方法可以注册成功回调函数,通过catch方法可以注册失败回调函数,通过finally方法可以注册最终回调函数。
当异步操作完成后,Promise会根据操作的结果调用相应的回调函数。

  async/await

  • async/await是一种用于处理异步操作的Promise语法糖

  • 基于Promise对象以一种更简单、易读的方式编写和处理异步代码

下面看看async/await的定义和使用

  • async关键字修饰的函数表示这是一个异步函数,会自动返回一个Promise对象

 async foo() {  
// 异步操作
return "result"
}

 await关键字

  • await关键字需要在async函数内部使用,等待一个Promise对象的解析结果,即Promise对象状态变为resolved(成功)或rejected(失败)

 async myAsyncFunction() : Promise<string> {  
const result: string = await new Promise((resolve) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('Hello, world!');
}
}, 3000)
})
console.log(result)
return result
}

Text(this.message)
.id('Submit')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
let res = this.myAsyncFunction().then((resolve => {
console.info("resolve is: " + resolve);
})).catch((error: BusinessError) => {
console.info("error is: " + error.message);
});
console.info("result is: " + res);
})

在async函数中使用await关键字可以实现类似同步代码的连续执行效果,而不需要嵌套使用回调函数或链式调用then方法。

 async/await的优点

  • 代码可读性更高,更接近同步代码的写法,易于理解和维护

  • 可以在代码中使用try/catch语句来捕获和处理异步操作产生的错误

  • 可以使用常规的控制流语法(如循环、条件语句)来组织和管理异步代码的执行顺序

  • async/await是依赖Promise对象来处理异步操作

  • async/await只是一种更加简洁和易读的语法,本质上仍然是基于Promise的异步编程模式

                                                    

    IO异步任务开发示例

import fs from '@ohos.file.fs';  
import common from '@ohos.app.ability.common';

async write(data: string, file: fs.File): Promise<void> {
fs.write(file.fd, data).then((writeLen: number) => {
console.log("write data length is: " + writeLen)
}).catch((error: BusinessError) => {
console.error(`write data failed. Code is ${error.code}, message is ${error.message}`);
})
}

async testWriteFile() : Promise<void> {
let context = getContext() as common.UIAbilityContext
let filePath: string = context.filesDir + "/logFile.txt"
let file: fs.File = await fs.open(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
this.write("Hello World", file).then(()=> {
console.log("write success")
}).catch((error: BusinessError) => {
console.error(`write data failed. Code is ${error.code}, message is ${error.message}`);
})
}


 03

多线程并发

Actor并发模型

  • Actor并发模型是一种用于并发计算的编程模型

  • 在该模型中,每一个线程都是一个独立Actor,每个Actor有自己独立的内存,Actor之间通过消息传递机制触发对方Actor的行为

  • Actor并发模型对比内存共享并发模型的优势在于不同线程间内存隔离,不会产生不同线程竞争同一内存资源的问题

  • 不需要考虑对内存上锁导致的一系列功能、性能问题,提升了开发效率

  • ArkTS语言选择的并发模型就是Actor

  • ArkTS提供了TaskPool和Worker两种并发能力,TaskPool和Worker都基于Actor并发模型实现

 TaskPool和Worker的实现特点对比

图片

TaskPool和Worker的适用场景对比

性能方面使用TaskPool会优于Worker,因此大多数场景推荐使用TaskPool。
TaskPool偏向独立任务维度,任务在线程中执行,不需要关注线程的生命周期。超长任务(大于3分钟)会被系统自动回收。

适用场景:

  • 运行时间超过3分钟的任务,需要使用Worker。

  • 有关联的一系列同步任务,例如在需要创建和使用不同句柄的场景中,每次创建的句柄需要永久保存。这种情况需要使用Worker来管理线程生命周期。

  • 需要频繁取消任务的场景,例如图库大图浏览,为了提升用户体验,同时缓存当前图片左右侧各2张图片。当用户往一侧滑动跳到下一张图片时,需要取消另一侧的一个缓存任务。这种情况下,使用TaskPool来管理任务会更适合。
    Worker偏向线程的维度,支持长时间占据线程执行,需要主动管理线程的生命周期。

  • 需要长时间占用线程执行的任务,例如网络请求、数据库操作等。这种情况下,使用Worker可以保持线程的稳定性和性能。

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

                                                            

    TaskPool运作机制

图片

  • TaskPool支持开发者在主线程封装任务抛给任务队列,系统会自动选择合适的工作线程,进行任务的分发及执行,再将结果返回给主线程

  • TaskPool提供简洁易用的接口,支持任务的执行和取消操作

  • 系统统一线程管理,结合动态调度及负载均衡算法,可以节约系统资源

                                                       

    Worker运作机制

    图片

  • Worker子线程与宿主线程拥有独立的实例,包含基础设施、对象、代码段

  • 每个Worker启动存在一定的内存开销,需要限制Worker的子线程数量

  • Worker子线程和宿主线程之间的通信是基于消息传递的

  • Worker通过序列化机制与宿主线程之间相互通信,完成命令及数据交互

 TaskPool注意事项

图片

 @Concurrent装饰器:校验并发函数

  • 在HarmonyOS中,@Concurrent装饰器用于标识一个方法需要在工作线程中执行

  • 该装饰器可以应用于普通的方法或者回调方法

  • 使用@Concurrent装饰器的方法会在一个工作线程中执行,不会阻塞主线程的运行。

  • 对于一些耗时操作或者需要与其他服务进行交互的方法非常有用

  • 在方法执行完成后,可以使用HarmonyOS提供的线程间通信机制将结果传递回主线程

                                     

    装饰器使用示例

import taskpool from '@ohos.taskpool';
@Concurrent
function add(num1: number, num2: number): number {
return num1 + num2
}

async function ConcurrentFunc(): Promise<void> {
try {
let task: taskpool.Task = new taskpool.Task(add, 1, 2)
console.log("taskpool res is:" + await taskpool.execute(task))
} catch (e) {
console.error("taskpool execute error is:" + e)
}
}

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

build() {

RelativeContainer() {

Text(this.message)
.id('Submit')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
ConcurrentFunc()
})
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
}
.height('100%')
.width('100%')
}
}

 同步任务

  • 在异步编程中,任务同步是指在多个异步任务之间进行协调和同步执行的过程。

  • 当存在多个异步任务需要按照一定的顺序或条件进行执行时,任务同步可以确保任务按照预期的顺序或条件进行执行。

常见的任务同步方式包括:

  • 回调函数:通过在一个异步任务完成后触发回调函数来执行下一个任务。

  • Promise/异步函数:使用Promise或异步函数的异步链式调用,通过then或await等关键字确保任务按顺序执行。

  • 线程间通信:通过消息队列或信号量等机制,在异步任务之间传递消息或信号,使得任务按特定的顺序或条件执行。

  • 锁或互斥体:使用锁或互斥体等同步机制,在异步任务之间实现互斥访问,确保任务按照顺序执行。

  • 任务同步的目的是确保异步任务能够按照一定的顺序或条件执行,以避免竞态条件、数据错误或逻辑错误。

                                                         

    使用taskpool处理同步任务

export default class Handle {  
private static singleton : Handle

public static getInstance() : Handle {
if (!Handle.singleton) {
Handle.singleton = new Handle();
}
return Handle.singleton;
}

public syncGet() {
return
}

public static syncSet(num: number) {
return
}
}

import taskpool from '@ohos.taskpool';
import Handle from './Handle';

// 定义并发函数,内部调用同步方法
@Concurrent
function func(num: number) {
// 调用静态类对象中实现的同步等待调用
Handle.syncSet(num)
// 或者调用单例对象中实现的同步等待调用
Handle.getInstance().syncGet()
return true
}

// 创建任务并执行
async function asyncGet() {
// 创建task并传入函数func
let task = new taskpool.Task(func, 1);
// 执行task任务,获取结果res
let res = await taskpool.execute(task);
// 对同步逻辑后的结果进行操作
console.info(String(res));
}

asyncGet()

 总结

  • 本次主要分享了关于鸿蒙开发中的异步并发和多线程并发的

  • 异步并发的介绍,和基本的用法,简单的举例;两种异步操作实现的对比

  • 多线程并发的简单概述,TaskPool和Worker两种多线程并发能力的介绍和对比,适用场景

  • 最后提及了TaskPool使用的简单例子








继续滑动看下一个
微鲤技术团队
向上滑动看下一个