HarmonyOS掌上记账APP开发实践第16篇:Worker 多线程与异步编程 — ArkTS 并发方案在记账应用中的实践

HarmonyOS掌上记账APP开发实践第16篇:Worker 多线程与异步编程 — ArkTS 并发方案在记账应用中的实践
Worker 多线程与异步编程 — ArkTS 并发方案在记账应用中的实践概述在移动应用中长时间运行的计算任务或阻塞式 I/O 操作不应占用主线程否则会导致界面卡顿、触摸响应延迟甚至触发系统 ANR应用无响应弹窗。HarmonyOS 提供了多种并发编程方案包括 Worker多线程、async/await异步协程和 Promise 链式处理。MoneyTrack 项目在数据处理、数据库查询和网络请求等场景中大量使用这些并发机制。本文分析其异步编程模式与并发设计。为什么需要并发在记账应用中用户期望每次操作都能得到即时反馈。例如切换账单月份时触发的大量账单统计计算、导出 CSV 时的文件写入操作、从远端同步账户数据时的网络请求等——这些任务如果全部在主线程中串行执行UI 将出现明显的卡死感。通过合理的并发设计可以将耗时任务放到后台线程确保 UI 线程始终能及时响应用户的触摸和滚动操作从而大幅提升用户体验。核心知识点1. async/await 异步模式ArkTS 的async/await是处理异步操作的标准方式。在 MoneyTrack 的 ViewModel 中几乎所有涉及数据库操作的方法都使用async/awaitpublicasyncrefreshBill(){awaitthis.dataProcessing.accountProcessing.loadAccounts();this.dataProcessing.getBillReport(this.checkedResource);try{emitter.emit({eventId:1});}catch(error){WindowUtil.dealAllError(error);}this.checkBudgetOverrun();}await关键字暂停当前函数的执行等待 Promise resolve 后继续执行下一行。这使得异步代码看起来像同步代码极大地提升了可读性。async/await 的错误传播async函数中的异常会隐式地将 rejected Promise 传递给调用者。如果refreshBill()被另一个async函数调用错误会自动向上传播publicasyncloadPageData(){try{awaitthis.refreshBill();// 如果 refreshBill 抛出异常这里会捕获到}catch(error){// 统一处理所有页面加载异常WindowUtil.dealAllError(error);}}这种自动传递特性让开发者可以在调用链的最顶层统一处理错误而不需要在每个层级都编写 try/catch大大简化了错误处理代码。2. Promise 链式处理在某些场景中MoneyTrack 使用 Promise 的.then().catch()链式调用。例如AssetsVM中的删除资产操作publicasyncdeleteAsset(id:number){contextUtil.getPromptAction()?.showDialog({...}).then(async(data){if(data.index1){awaitAccountingDB.deleteAssetAccount(id);awaitthis.assetProcessing.getAssetReport(...);}}).catch((error:BusinessError){WindowUtil.dealAllError(error);});}弹窗 API 返回 Promise后续的数据库操作和页面刷新通过链式调用串行执行并通过.catch()统一处理异常。3. Promise.all 并发执行当需要同时发起多个相互独立的异步操作时Promise.all可以显著缩短总体等待时间publicasyncloadDashboardData(){// 三个数据源之间没有依赖关系可以并发加载const[accounts,budgets,bills]awaitPromise.all([this.accountProcessing.loadAccounts(),// 加载账户列表this.budgetProcessing.loadBudgets(),// 加载预算数据this.billProcessing.loadRecentBills(),// 加载近期账单]);// 所有数据加载完成后一次性更新 UIthis.updateDashboard(accounts,budgets,bills);}适用场景仪表盘首页的多数据面板加载、批量导出时同时查询多个账期数据、同步多个远端 API 请求等。使用Promise.all时需要注意任何一个 Promise 被 reject整体结果就会进入 catch 分支因此适合全有或全无的场景。4. Worker 多线程对于计算密集型的任务如大数据量的账单统计可以通过 Worker 创建新线程避免阻塞主线程。在 MoneyTrack 中AccountingDB的数据库操作封装为异步调用底层的耗时操作通过 Worker 线程执行确保 UI 流畅。Worker 的创建与通信Worker 线程与主线程通过消息机制进行通信。以下是典型的使用流程// 主线程创建 Worker 并发送消息constworkernewworker.ThreadWorker(entry/ets/workers/ReportWorker.ets);worker.onmessage(event:MessageEvents){constresultevent.data;// 接收 Worker 返回的处理结果console.info(统计完成:${JSON.stringify(result)});worker.terminate();// 任务完成后销毁 Worker};worker.onerror(error:ErrorEvents){console.error(Worker 出错:${error.message});worker.terminate();};// 向 Worker 发送待处理的数据worker.postMessage({type:generateReport,month:2026-06,userId:1001});// Worker 线程 (ReportWorker.ets) workerPort.onmessage (event: MessageEvents) { const { type, month, userId } event.data; // 执行耗时计算 const report heavyCalculation(month, userId); workerPort.postMessage(report); // 将结果发回主线程 };主线程与 Worker 线程的交互流程Worker 线程主线程 (UI)Worker 线程主线程 (UI)进行大数据量计算不阻塞 UI 线程用户触发操作如切换月份postMessage(计算任务参数)可继续响应其他操作postMessage(计算结果)接收结果并更新界面UI 线程与 Worker 线程的数据传递限制在 ArkTS 中主线程与 Worker 线程之间的数据传递有明确的限制序列化传递数据通过结构化克隆算法Structured Clone进行深拷贝传递的是数据的副本而非引用因此修改 Worker 中的数据不会影响主线程的原始数据。限制类型Function、Error、DOM 节点等无法传递。仅支持普通对象、Array、Map、Set、ArrayBuffer 等可序列化的类型。性能考量大数据量如超过 100KB 的对象的传递序列化开销较大建议仅传递必要的最小数据集或使用 ArrayBuffer 进行高效传输。5. 异常处理策略MoneyTrack 采用统一的异常处理策略所有异步操作都可能抛出异常通过try/catch或.catch()捕获后统一交给WindowUtil.dealAllError()处理catch(error){WindowUtil.dealAllError(error);}这确保了错误被一致地记录和处理不会导致应用崩溃。项目案例d:\HarmonyOS\WorkSpace\MoneyTrack1.0.3\features\home\src\main\ets\viewmodels\HomeVM.ets中refreshBill()-checkBudgetOverrun()的异步链展示了典型的数据库操作流程。d:\HarmonyOS\WorkSpace\MoneyTrack1.0.3\features\assets\src\main\ets\viewmodels\AssetsVM.ets的deleteAsset()方法展示了 Promise 链式调用和统一错误处理。最佳实践数据库操作统一 async所有涉及数据库增删改查的方法都应声明为async返回Promise以便调用方能够通过await等待操作完成避免回调地狱。错误集中处理定义一个全局的异常处理工具如WindowUtil.dealAllError所有异步操作的异常统一汇聚到该工具中避免分散的 try/catch 导致代码冗余。Worker 任务粒度控制创建 Worker 的开销较大约几十毫秒应仅在耗时超过 100ms 的计算密集型任务中使用轻量操作直接在主线程用 async/await 完成即可。合理选择并发模式无依赖的并行任务用Promise.all有先后依赖的串行任务用async/await计算密集型任务用 Worker。及时释放 Worker 资源Worker 任务完成后应调用worker.terminate()或worker.close()释放线程资源避免内存泄漏。总结HarmonyOS 为 ArkTS 应用提供了层次丰富的并发编程方案async/await让异步代码的编写和阅读如同同步代码般直观Promise.all让无依赖的并行任务高效协同Worker 线程则解决了计算密集型任务阻塞 UI 的问题。在 MoneyTrack 的实践中这三种方案各司其职——数据库操作和 UI 交互以async/await为主多数据源加载使用Promise.all大数据量统计则由 Worker 在后台处理。理解并合理运用这些并发机制是构建流畅、可靠 HarmonyOS 应用的关键能力。参考文档ArkTS 并发编程指南Workerasync/await 异步编程最佳实践Promise API 参考