软件教程 2025年08月6日
0 收藏 0 点赞 790 浏览 5578 个字
摘要 :

文章目录 一、并发的基本概念 二、ArkTS的并发策略 (一)异步并发 (二)多线程并发 三、ArkTS并发策略对比 (一)异步并发要点总结 (二)多线程并发要点总结 四……




  • 一、并发的基本概念
  • 二、ArkTS的并发策略
    • (一)异步并发
    • (二)多线程并发
  • 三、ArkTS并发策略对比
    • (一)异步并发要点总结
    • (二)多线程并发要点总结
  • 四、应用场景选择
  • 五、异步并发详解(Promise和async/await)
    • (一)异步并发核心概念
    • (二)Promise与async/await对比
    • (三)Promise核心要点
    • (四)async/await核心要点
    • (五)错误处理对比
  • 六、多线程并发(TaskPool和Worker)
    • (一)任务池(TaskPool)
    • (二)Worker
    • (三)TaskPool vs Worker核心对比
    • (四)场景选择口诀
    • (五)错误处理对比
    • (六)记忆要点

    鸿蒙ArkTS的并发机制能让应用在处理多个任务时更加高效,避免卡顿,提升用户体验。下面,我们就来详细了解一下ArkTS是如何实现并发的。

    一、并发的基本概念

    并发指的是在同一时间段内,有多个任务同时在执行。这里要注意区分并发和并行:

    • 并发:从逻辑上来说,多个任务像是在同时执行。在单核设备上,CPU会在任务处于休眠或者进行I/O操作等状态时,切换去执行其他任务,以此来提高CPU的资源利用率;而在多核设备上,这些任务是可以真正并行执行的。
    • 并行:这是指物理上的同时执行,不过这需要依赖多核设备才能实现。

    并发的主要目标就是提升应用的响应速度和帧率,避免主线程被阻塞,从而让用户操作更加流畅。

    二、ArkTS的并发策略

    为了提升应用性能,避免耗时任务影响UI主线程,ArkTS提供了两种并发策略:异步并发和多线程并发。

    (一)异步并发

    异步代码在执行过程中,会在某个阶段暂停,之后在合适的时间点继续执行。在同一时刻,实际上只有一段代码在运行。ArkTS借助Promise和async/await实现异步并发,这种方式适用于单次I/O任务的开发场景,比如网络请求或者文件读写操作。

    (二)多线程并发

    多线程并发允许在同一时间段内,有多段代码同时执行。这样,在UI主线程继续响应用户操作、更新UI的同时,后台线程可以去执行那些比较耗时的操作,防止应用出现卡顿现象。ArkTS通过TaskPool(线程池)和Worker(独立线程)来提供多线程并发能力,很适合处理CPU密集型任务以及其他耗时操作,如图像处理等场景。

    三、ArkTS并发策略对比

    下面通过表格来详细对比这两种并发策略:

    策略 异步并发 多线程并发
    实现方式 Promise + async/await TaskPool(线程池) + Worker(独立线程)
    执行特性 单线程,代码分段暂停和恢复 多线程并行执行
    适用场景 单次I/O任务(如网络请求、文件读写) CPU密集型任务、耗时操作(如图像处理)
    线程阻塞 不会阻塞主线程,但代码是串行执行 后台线程执行,能完全避免主线程卡顿
    资源开销 较低(因为没有线程创建的开销) 较高(需要管理线程的生命周期)

    (一)异步并发要点总结

    • 本质:异步并发本质上是单线程任务调度,通过事件循环来实现非阻塞操作。
    • 优势:这种方式比较轻量级,对于简单的I/O任务处理起来很合适。
    • 限制:它无法充分利用多核性能,如果处理复杂任务,可能会出现回调嵌套的情况,不过可以使用async/await来优化。

    (二)多线程并发要点总结

    • 核心对象
      • TaskPool:主要作用是复用线程,避免频繁创建和销毁线程,从而降低资源消耗。
      • Worker:作为独立线程,适合执行长时间运行的任务。
    • 数据传输
      • 基本类型:基本类型的数据可以直接进行拷贝传输。
      • 复杂对象:复杂对象在传输时,需要进行序列化(比如使用JSON),或者使用共享内存(不过要谨慎使用)。要注意,线程间通信得通过消息传递的方式,避免共享资源竞争。

    四、应用场景选择

    不同的任务类型适合不同的并发策略,具体如下:

    任务类型 推荐策略 示例场景
    单次I/O操作 异步并发 请求API、读取本地文件
    CPU密集型 多线程(TaskPool) 图像处理、数据加密解密
    长时任务 多线程(Worker) 后台下载、持续日志写入
    UI交互响应 主线程 + 异步 按钮点击后非阻塞更新UI

    五、异步并发详解(Promise和async/await)

    (一)异步并发核心概念

    异步并发本质上是单线程非阻塞任务调度,同一时间只有一段代码在执行。它依靠事件循环来实现任务的挂起和恢复,这样就能避免主线程被阻塞。不过,它不太适合处理CPU密集型任务,因为这类任务会阻塞主线程。

    (二)Promise与async/await对比

    特性 Promise async/await
    本质 异步状态管理对象 Promise的语法糖,让异步代码编写更简单
    代码风格 通过链式调用(.then().catch())来处理 采用同步式写法,逻辑更直观
    错误处理 使用.catch()捕获异常 利用try/catch捕获异常
    返回值 返回Promise对象 返回Promise对象
    可读性 当回调嵌套复杂时,可读性较差 代码线性执行,可读性高

    (三)Promise核心要点

    1. 三种状态:Promise有三种状态,分别是pending(进行中)、fulfilled(成功)和rejected(失败) 。状态一旦从pending变为fulfilled或者rejected,就不能再改变了。
    2. 基本用法
    const promise = new Promise((resolve, reject) => { 
      // 异步操作(如setTimeout、文件读写)
      if (成功) resolve(result); 
      else reject(error); 
    });
    
    1. 链式调用
    promise.then(result => { ... })
      .catch(error => { ... });
    
    1. 关键注意点:如果没有处理reject状态,会触发unhandledrejection事件,所以需要进行全局监听:
    errorManager.on(\'error\', (err) => { ... });
    

    下面是一个完整示例,创建了一个Promise对象并模拟异步操作:

    const promise: Promise<number> = new Promise((resolve: Function, reject: Function) => {
    setTimeout(() => { 
    const randomNumber: number = Math.random(); 
    if (randomNumber > 0.5) { 
    resolve(randomNumber); 
    } else {    
    reject(new Error(\'Random number is too small\'));  }}, 1000);})
    

    上述代码使用setTimeout模拟异步操作,1秒钟后随机生成一个数字。如果数字大于0.5,就执行resolve回调函数并传递随机数;否则执行reject回调函数并传递错误对象。

    (四)async/await核心要点

    1. 语法规则
      • 用async标记异步函数,比如async function fetchData()
      • 使用await暂停函数执行,直到Promise完成,例如await promise
    2. 代码示例
    async function myAsyncFunc(): Promise<string> {
      try {
        const result = await new Promise(resolve => {
          setTimeout(() => resolve(\'Hello\'), 3000);
        });
        return result;
      } catch (error) {
        console.error(error);
        throw error; // 抛出异常会被外层catch捕获
      }
    }
    
    1. 优势与限制
      • 优势:使用async/await可以让代码扁平化,有效避免回调地狱。
      • 限制:如果在循环中滥用await,可能会导致性能下降。
        下面是一个完整示例,模拟以同步方式执行异步操作,3秒钟后返回字符串:
    async function myAsyncFunction(): Promise<string> { 
    const result: string = await new Promise((resolve: Function) => {   
    setTimeout(() => {     
    resolve(\'Hello, world!\');    }, 3000);  });
    console.info(result); // 输出: Hello, world!  return result;}
    @Entry@Componentstruct Index { 
    @State message: string = \'Hello World\'; 
    build() {   
    Row() {    
    Column() {      
    Text(this.message)        
    .fontSize(50)         
    .fontWeight(FontWeight.Bold)          
    .onClick(async () => {            
    let res = await myAsyncFunction();            
    console.info(\"res is: \" + res);         
    })}      
    .width(\'100%\')
    }    
    .height(\'100%\') 
    }}
    

    (五)错误处理对比

    方式 Promise async/await
    成功处理 .then(result => { … }) const result = await promise
    失败处理 .catch(error => { … }) try { … } catch (error) { … }
    全局异常捕获 监听unhandledrejection事件 结合try/catch与全局监听

    六、多线程并发(TaskPool和Worker)

    (一)任务池(TaskPool)

    1. 作用:为应用提供多线程运行环境,降低资源消耗,提升系统整体性能。开发者无需操心线程实例的生命周期。
    2. 运作机制:可以参考相关官方文档中的图示来深入理解其运作原理鸿蒙ArkTS的并发实现原理与应用
    3. 核心要点
      • 使用场景:适用于CPU密集型的短任务,如图像处理、加密解密;以及高频小任务,能避免频繁创建线程带来的开销。
      • 关键规则
        • 任务函数必须用@Concurrent修饰。
        • 单任务函数执行时间(不含异步I/O)应小于等于3分钟。
        • 数据传输仅支持可序列化类型,像基本类型、ArrayBuffer等。
      • 示例代码
    // 定义任务函数(必须@Concurrent)
    @Concurrent
    function add(a: number, b: number): number {
      return a + b;
    }
    
    // 提交任务到线程池
    async function runTask() {
      const task = new taskpool.Task(add, 1, 2);
      const result = await taskpool.execute(task);
      console.log(`Task结果: ${result}`); // 输出3
    }
    
    - **注意事项**:
        - 任务函数内禁止访问外部变量,避免闭包。
        - 只能使用线程安全的API,不能直接操作UI。
    

    (二)Worker

    1. 作用:为应用提供多线程运行环境,能让应用在后台线程执行耗时操作,与宿主线程分离,避免阻塞宿主线程,比如计算密集型或高延迟任务。
    2. 运作机制:具体运作机制可查看官方文档中的相关图示。鸿蒙ArkTS的并发实现原理与应用
    3. 核心要点
      • 使用场景:适合长时任务,如后台下载、持续数据同步;以及有明确控制线程生命周期需求的场景。
      • 关键规则
        • 最多同时运行64个Worker。
        • 必须手动调用close()销毁Worker。
        • Worker文件需要放在指定目录,例如entry/ets/workers/
      • 示例代码
    // 主线程:创建Worker并通信
    const worker = new worker.ThreadWorker(\'entry/ets/workers/myWorker.ets\');
    worker.postMessage(\'开始任务\'); // 发送消息
    worker.onmessage = (e) => { 
      console.log(`收到Worker回复: ${e.data}`); 
    };
    
    // Worker线程(myWorker.ets)
    workerPort.onmessage = (e) => {
      workerPort.postMessage(\'任务完成\'); // 回复消息
    };
    
    - **注意事项**:
        - 如果存在多级Worker,父Worker销毁前要先销毁子Worker。
        - 所有Worker的内存总和不能超过1.5GB或者物理内存的60%。
    

    (三)TaskPool vs Worker核心对比

    特性 TaskPool Worker
    本质 线程池动态调度(任务队列 + 线程复用) 独立线程(需手动管理生命周期)
    适用场景 短时、高频的CPU密集型任务(如计算) 长时、独立的后台任务(如下载、日志)
    线程数量 动态扩容(上限为设备物理核数) 最多64个,需手动销毁
    开发复杂度 低(系统自动管理线程) 较高(需处理线程创建、销毁、通信)
    数据传输限制 16MB(支持序列化对象) 16MB(支持序列化对象)
    生命周期 任务结束自动释放线程资源 需手动调用close()或terminate()销毁
    错误处理 通过Promise的.catch()捕获 通过onerror回调捕获

    (四)场景选择口诀

    可以用这样一句口诀来帮助选择合适的并发方式:“TaskPool扛短快,Worker长时独立在;高频计算用池化,下载日志Worker带。”

    (五)错误处理对比

    策略 错误捕获方式 示例
    TaskPool 通过Promise的.catch() taskpool.execute(task).catch(e => {})
    Worker 通过onerror回调 worker.onerror = (err) => { … }

    (六)记忆要点

    • TaskPool:主要特点是线程池复用,适用于短任务,使用@Concurrent装饰器,并且参数需要是可序列化的。
    • Worker:作为独立线程,需要手动管理生命周期,主从线程之间通过postMessage进行通信。

    如果想要深入了解TaskPool和Worker的具体实现特点,可以查阅官方文档获取更详细的信息。通过合理运用这两种并发策略,我们能够更好地优化鸿蒙应用的性能。

微信扫一扫

支付宝扫一扫

版权: 转载请注明出处:https://www.zuozi.net/6883.html

管理员

相关推荐
2025-08-06

文章目录 一、Promise基础回顾 二、Promise 与 axios 结合使用场景及方法 (一)直接返回 axios …

270
2025-08-06

文章目录 一、模块初始化时的内部机制 二、常见导出写法的差异分析 (一)写法一:module.exports…

108
2025-08-06

文章目录 一、ResizeObserver详解 (一)ResizeObserver是什么 (二)ResizeObserver的基本用法 …

684
2025-08-06

文章目录 一、前期准备工作 (一)下载相关文件 (二)安装必要工具 二、处理扣子空间生成的文件…

340
2025-08-06

文章目录 一、官方文档 二、自动解包的数据类型 ref对象:无需.value即可访问 reactive对象:保持…

371
2025-08-06

文章目录 一、Hooks的工作原理 二、在if语句中使用Hook会出什么岔子? 三、React官方的Hook使用规…

844
发表评论
暂无评论

还没有评论呢,快来抢沙发~

助力内容变现

将您的收入提升到一个新的水平

点击联系客服

在线时间:08:00-23:00

客服QQ

122325244

客服电话

400-888-8888

客服邮箱

122325244@qq.com

扫描二维码

关注微信客服号