化:Future與Callback異步模型實戰(zhàn)解析)
作為一個在 HarmonyOS 應(yīng)用開發(fā)里泡了挺久的人我對“卡頓”兩個字特別敏感。大部分剛上手的朋友遇到性能問題第一反應(yīng)就是優(yōu)化算法、精簡布局但很多時候真正的元兇不是算法本身而是你讓 CPU 干重活的方式不對。HarmonyOS 的異步編程模型里Future 和 Callback 看似只是回調(diào)風(fēng)格的區(qū)別但在優(yōu)化 CPU 密集計算這件事上兩者的用法、邊界和收益差別非常大。這篇內(nèi)容我不打算講枯燥的 API 手冊而是從一個真實計算場景出發(fā)聊聊怎么用這兩套模型把耗時任務(wù)從主線程手里搶出來讓它該并行并行、該回報回報最終讓界面保持流暢。1. 先搞清楚CPU 計算和主線程之間的“賬本”怎么算1.1 主線程不是不能算而是預(yù)算太少HarmonyOS 應(yīng)用的主線程也被叫做 UI 線程承擔(dān)著事件分發(fā)、布局測量、繪制渲染、動畫回調(diào)這一大堆活。設(shè)備屏幕刷新率通常是 60Hz 或 90Hz、120Hz按 60Hz 算留給每一幀的時間大約是 16.6 毫秒。也就是說你的主線程里所有任務(wù)加在一起必須在這 16.6 毫秒里干完否則就會掉幀、卡頓、甚至觸發(fā)系統(tǒng)層面的 ANR。很多人對“計算任務(wù)”沒有概念覺得循環(huán)幾十萬次也就一兩毫秒。實際上當(dāng)你要處理圖像濾波、音頻特征提取、復(fù)雜物理模擬、大量數(shù)據(jù)統(tǒng)計時一次 CPU 密集運算動輒幾百毫秒甚至幾秒。這種任務(wù)放在主線程里跑就像一個人在單行道上搬磚后面所有的車觸摸事件、繪制指令、動畫回調(diào)都得等著。我見過一個案例某個統(tǒng)計頁面在進入時會對過去一年的數(shù)據(jù)做聚合排序直接在生命周期回調(diào)里同步執(zhí)行結(jié)果進入頁面白屏了將近兩秒用戶都以為應(yīng)用死了。所以第一步不是急著寫異步代碼而是承認(rèn)一個事實CPU 密集計算不應(yīng)該出現(xiàn)在主線程的預(yù)算里。異步編程解決的是“結(jié)果什么時候回來”的問題而線程切換解決的是“任務(wù)在哪里執(zhí)行”的問題。兩者必須配合使用。1.2 異步不等于不卡關(guān)鍵是“換線程”HarmonyOS 提供了兩套異步手段Callback 和 FuturePromise 是 Future 的一種典型實現(xiàn)。很多初學(xué)者以為把同步代碼改成回調(diào)就是異步了這是誤區(qū)。比如下面這種寫法setTimeout(() { // 在這里做小時級別的 CPU 密集循環(huán) for (let i 0; i 500000000; i) { // 大量浮點運算 } }, 0);看著像是異步了但實際上定時器回調(diào)仍然在主線程執(zhí)行密集循環(huán)照樣把主線程堵死。真正的做法是把循環(huán)扔到子線程去跑主線程只負(fù)責(zé)發(fā)起任務(wù)、等待結(jié)果。HarmonyOS 里我們通常要借助任務(wù)池或 Worker 機制創(chuàng)建子線程執(zhí)行環(huán)境然后用 Future 或 Callback 把結(jié)果收回來。以我的經(jīng)驗先想清楚“任務(wù)在哪執(zhí)行”再想“用什么回調(diào)風(fēng)格接收結(jié)果”順序不能反。你甚至可以把任務(wù)丟給 Worker然后主線程通過一個 Future 等待返回這樣界面該渲染渲染該響應(yīng)響應(yīng)計算在后臺悄悄進行。1.3 判斷一個計算適不適合異步化的標(biāo)準(zhǔn)不是所有 CPU 計算都值得異步化。我一般用三個標(biāo)準(zhǔn)來判斷執(zhí)行時長單次耗時超過 16 毫秒就該考慮超過 100 毫秒必須處理。是否可拆分如果計算能被切成多個獨立片段比如分塊統(tǒng)計、分批渲染那異步化的收益會更大因為可以并發(fā)。耦合度如果計算結(jié)果會被下一個步驟直接消費且中間沒有 UI 更新點那么至少要保證它不在主線程執(zhí)行否則可以做成同步但挪到子線程。這三條標(biāo)準(zhǔn)看起來簡單但在實際項目中非常有用。它能避免你陷入一個常見誤區(qū)為了一段 3 毫秒的計算引入復(fù)雜的異步流程得不償失。2. Callback 與 Future同樣是異步但控制的“顆粒度”完全不一樣2.1 Callback 模型簡單直接但嵌套深了會窒息Callback 就是你把一個函數(shù)作為參數(shù)傳給另一個函數(shù)等異步操作完成后被調(diào)用。HarmonyOS 里很多底層能力比如傳感器數(shù)據(jù)讀取、文件 IO都提供 Callback 風(fēng)格接口。它最大的優(yōu)點是真的簡單發(fā)起任務(wù)時直接把下一步要做的事寫好邏輯鏈路直觀。但 Callback 的缺點在復(fù)雜業(yè)務(wù)里格外突出。一旦你的 CPU 計算需要串聯(lián)多個步驟比如先讀取數(shù)據(jù)再過濾再計算特征再寫回緩存回調(diào)層級會越套越深代碼的可讀性急劇下降。更麻煩的是異常處理每一步都可能出錯你得在每個回調(diào)里單獨判斷很容易漏掉分支。所以我對 Callback 的使用原則是適合簡短的一次性任務(wù)或者是需要高頻次進度反饋的場景。比如一個計算函數(shù)支持分片回調(diào)每算完 10% 就通知一次進度這種用 Callback 非常自然。2.2 Future 模型鏈?zhǔn)浇M合與統(tǒng)一異常處理的價值FutureHarmonyOS 里常以 Promise 形式出現(xiàn)代表一個尚未完成但最終會 resolve 或 reject 的操作。它解決了 Callback 兩個核心痛點第一可以通過 then 鏈把多個異步步驟串起來避免嵌套地獄第二異常可以冒泡到鏈?zhǔn)轿膊拷y(tǒng)一處理。給你看一個直觀對比。假設(shè)我們的 CPU 計算流程是從緩存拿原始數(shù)據(jù) → 數(shù)據(jù)標(biāo)準(zhǔn)化 → 執(zhí)行核心計算 → 格式化結(jié)果。Callback 寫法大概是這樣function runWithCallback(callback: (err: Error | null, result?: number[]) void) { readCache((cacheErr, rawData) { if (cacheErr) { callback(cacheErr); return; } normalize(rawData, (normErr, normed) { if (normErr) { callback(normErr); return; } compute(normed, (computeErr, output) { if (computeErr) { callback(computeErr); return; } format(output, (formatErr, final) { if (formatErr) { callback(formatErr); return; } callback(null, final); }); }); }); }); }一旦進入四層嵌套不管是閱讀還是修改人都會很吃力。用 Future 改寫后鏈條明顯清晰很多function runWithFuture(): Promisenumber[] { return readCache() .then(rawData normalize(rawData)) .then(normed compute(normed)) .then(output format(output)) .catch(err { console.error(計算流程失敗: ${err.message}); throw err; }); }2.3 如何根據(jù)計算場景選擇合理的 API 風(fēng)格沒有銀彈但有一個簡單實用的選擇矩陣場景特征推薦風(fēng)格理由一次性的后臺計算結(jié)果只用于 UI 展示Future鏈?zhǔn)竭壿嬊逦惓H菀捉y(tǒng)一處理計算分片執(zhí)行需要實時反饋進度Callback每次分片完成后自然觸發(fā)回調(diào)天然貼合進度語義多個計算步驟串聯(lián)且每步依賴上一步Futurethen 鏈非常直觀中間狀態(tài)通過閉包傳遞多個獨立計算需要并行最后匯總Future可以用并發(fā)等待機制如 Promise.all聚合結(jié)果與系統(tǒng)底層 API 交互接口本身只提供 CallbackCallback順著平臺接口風(fēng)格走不要強行包裝我個人的傾向是核心業(yè)務(wù)邏輯盡量用 Future寧可包裝一層也要保證代碼可維護只有進度類回調(diào)這種天然事件驅(qū)動的場景才直接用 Callback。理由很簡單CPU 計算的異步流程通常不止一步后續(xù)大概率會擴展Future 的擴展性更好。3. 用 Future 把一次 CPU 密集計算改造成流水線3.1 從一次真實的掉幀現(xiàn)場說起我之前處理過的某圖像處理 Demo需要在一張 4000×3000 的圖片上做邊緣檢測。原始實現(xiàn)非常直接在拿到圖片數(shù)據(jù)后同步執(zhí)行一個雙層循環(huán)對每個像素做灰度矩陣運算。在真機上測試整段計算耗時約 780 毫秒。結(jié)果就是用戶觸發(fā)處理按鈕后界面像被凍結(jié)一樣進度條紋絲不動直到計算結(jié)束才一次性刷新。這還只是視覺上的問題期間如果用戶又滑動了頁面或者點了別的按鈕事件全部堆積在主線程隊列等計算結(jié)束后瘋狂補執(zhí)行畫面會瞬間亂跳。這就是一個典型的“CPU 計算堵死主線程”問題。我的目標(biāo)很明確把 780 毫秒的計算時間徹底移出主線程并通過 Future 重組流程讓 UI 在計算期間保持響應(yīng)。3.2 拆解步驟同步計算如何變成 Future 鏈改造前我把整個流程拆成了三個獨立的計算單元單元 A把原始圖片數(shù)據(jù)轉(zhuǎn)為灰度矩陣。單元 B對灰度矩陣執(zhí)行邊緣檢測卷積。單元 C把處理后的矩陣重新編碼成可顯示的圖像數(shù)據(jù)。這三個單元都可以獨立運行且每個單元耗時占比大約是 20%、60%、20%。最關(guān)鍵的是單元 A 和單元 C 涉及像素格式轉(zhuǎn)換屬于純計算單元 B 是卷積涉及大量浮點乘加它們是天然適合子線程執(zhí)行的 CPU 密集任務(wù)。我用 Future 重新設(shè)計了流程。核心思路是每個單元都返回一個 Promise后續(xù)單元通過 then 接收前一個單元的結(jié)果最終在鏈的末端把結(jié)果交給 UI 層。關(guān)鍵點在于計算本身要放到子線程環(huán)境里而不是直接在 Promise 構(gòu)造器里同步執(zhí)行。async function processImage(source: PixelData): PromiseImageData { // 灰度轉(zhuǎn)換在子線程中執(zhí)行 const grayMatrix: number[][] await runInTaskPool(() convertToGray(source)); // 邊緣檢測卷積同樣在子線程中執(zhí)行 const edgeMatrix: number[][] await runInTaskPool(() applySobel(grayMatrix)); // 結(jié)果編碼第三次子線程執(zhí)行 const output: ImageData await runInTaskPool(() encodeToImage(edgeMatrix)); return output; }這段代碼里的runInTaskPool是對 HarmonyOS 任務(wù)池的封裝它接收一個函數(shù)把函數(shù)投遞到子線程執(zhí)行并返回一個 Promise。主線程這邊調(diào)用時await會掛起當(dāng)前流程但事件循環(huán)沒有被阻塞UI 依然能正常渲染。3.3 每一步為什么要這樣設(shè)計這里我想重點解釋兩個容易被忽略的設(shè)計點第一為什么每個計算單元都單獨投遞到任務(wù)池而不是把三個單元合成一個大函數(shù)投進去因為分開投遞后每一步之間主線程都有機會處理其他事件比如更新進度提示、響應(yīng)用戶操作。如果合成一個大函數(shù)子線程里跑完所有計算再一次性返回用戶看到的就是“點了按鈕后長時間無反饋”交互體驗依然不好。第二為什么用await而不是在 then 里嵌套從語義上講await是 Future 的語法糖但它讓代碼看起來更像同步邏輯調(diào)試也更方便。不過要注意await會創(chuàng)建隱式上下文如果你在循環(huán)里寫await要小心每次迭代都可能被調(diào)度器拆成多次任務(wù)反而增加開銷。在我們的場景里沒有循環(huán)三個await串行執(zhí)行簡潔且可控。3.4 改造后的效果與鏈路開銷改造后我在同一臺真機上重新測試主線程的卡頓徹底消失因為所有像素循環(huán)都在子線程執(zhí)行。圖像處理總耗時反而從 780 毫秒降到了 810 毫秒左右略微增加這是線程調(diào)度和線程間數(shù)據(jù)傳遞的固有開銷但你換來了完全不掉幀的交互體驗。這里想提醒一下不是所有異步化改造都會讓計算變快很多時候只是讓計算不阻塞 UI性能數(shù)字反而會微降。衡量標(biāo)準(zhǔn)應(yīng)該是用戶體驗而不是單純的 FLOPS。如果你非得追求總耗時也下降那就要引入并發(fā)把相互獨立的計算單元并行執(zhí)行而不是串行。這就涉及到第 5 部分要講的多核調(diào)度。4. 用 Callback 做分片進度回報從“轉(zhuǎn)圈”到“知道跑到第幾步”4.1 為什么進度反饋在 CPU 計算中如此重要Future 鏈能很好地組織流程但它不適合做高頻進度通知。原因很簡單Future 只關(guān)心“最終的結(jié)果”中間狀態(tài)需要通過額外手段透出。比如你可以在每個 then 階段手動調(diào)用一個進度函數(shù)但這種做法粒度太粗只能報“灰度轉(zhuǎn)換完成”“卷積完成”無法報“卷積計算到第 37%”。當(dāng)任務(wù)耗時較長用戶最需要的是確定性和掌控感。一個不動的進度條讓人焦慮一個不斷更新但準(zhǔn)確反映狀態(tài)的進度條會大幅提升耐心。Callback 在這個場景下的優(yōu)勢就體現(xiàn)出來了你可以把它設(shè)計成在每個分片計算完成后被調(diào)用攜帶當(dāng)前進度、剩余時間預(yù)估、甚至當(dāng)前處理的數(shù)據(jù)塊信息。4.2 一個典型的分片 Callback 設(shè)計假設(shè)我們的 CPU 計算是對一個超大數(shù)組做排序加過濾統(tǒng)計。數(shù)組長度是 1000 萬條記錄單次全量處理大約需要 3 秒。我先把它切成 N 個分片每個分片在子線程里單獨處理處理完成后通過 Callback 上報一次進度。interface ProgressInfo { stage: string; percent: number; } function processLargeArray( data: number[], onProgress: (info: ProgressInfo) void, onComplete: (result: number) void, onError: (err: Error) void ) { const CHUNK_SIZE 250000; const totalChunks Math.ceil(data.length / CHUNK_SIZE); let processedChunks 0; let stageResult 0; for (let i 0; i totalChunks; i) { const chunk data.slice(i * CHUNK_SIZE, (i 1) * CHUNK_SIZE); runInTaskPool(() { // 對分片排序并累加統(tǒng)計 chunk.sort((a, b) a - b); let sum 0; for (const item of chunk) { sum item; } return sum; }).then(chunkSum { stageResult chunkSum; processedChunks; onProgress({ stage: sort-and-sum, percent: Math.floor((processedChunks / totalChunks) * 100) }); if (processedChunks totalChunks) { onComplete(stageResult); } }).catch(err { onError(err); }); } }注意這里有個并發(fā)隱患這個循環(huán)其實是在同時發(fā)起多個子線程任務(wù)每個分片并行執(zhí)行?;卣{(diào)觸發(fā)的順序不一定和切分順序一致所以進度計算用的是“已完成分片數(shù) / 總分片數(shù)”而不是假設(shè)分片 i 一定先于分片 i1 完成。4.3 進度上報的頻率控制別讓回調(diào)轟炸 UI雖然 Callback 很好用但如果不做頻率控制進度回調(diào)可能一秒鐘觸發(fā)幾十次UI 層頻繁更新進度條反而引起額外的主線程負(fù)擔(dān)。我常用的做法是閾值節(jié)流只有當(dāng)前進度和上一次上報進度之間的差值超過一定比例比如 5%才真正觸發(fā) UI 更新。另外如果進度更新只是刷新一個文本和一條進度條我傾向于把 UI 更新函數(shù)包在一個輕量級節(jié)流器里比如每 100 毫秒最多更新一次。這樣既保證用戶能看到變化又不會讓主線程被進度刷新本身拖累。其實你仔細(xì)想想就明白進度條本身是給人看的人眼對 100 毫秒內(nèi)的變化感知很弱所以 10Hz 的刷新率在進度反饋場景已經(jīng)足夠了。過度刷新純屬浪費。4.4 取消機制與回調(diào)安全的收尾處理Callback 方案還有一個容易被忽略的問題如果用戶在中途退出了頁面或者不再需要計算結(jié)果回調(diào)仍然會被觸發(fā)可能造成內(nèi)存泄漏或者訪問已銷毀 UI 組件。我在實際開發(fā)中養(yǎng)成了一個習(xí)慣凡是使用 Callback 的長任務(wù)一定會設(shè)計一個取消令牌或狀態(tài)標(biāo)志。let cancelled false; function cancelProcessing() { cancelled true; } function processLargeArray(...) { // 在每個分片回調(diào)里先檢查 cancelled // 如果已取消就不要再累加結(jié)果也不再更新進度 if (cancelled) { return; } }更進一步回調(diào)里如果要更新 UI一定要通過安全的 UI 更新通道并先判斷頁面或組件實例是否仍然存活。這種細(xì)節(jié)看起來小但在長耗時計算中非常關(guān)鍵因為用戶大概率會在計算期間做其他操作生命周期發(fā)生變化是常態(tài)。5. 把計算任務(wù)打散到多核并發(fā)參數(shù)與真實收益5.1 并發(fā)數(shù)不是越大越好線程切換的開銷賬本當(dāng)我提到可以對分片做并行處理時很多人第一反應(yīng)是“把分片數(shù)量設(shè)得越多越好并發(fā)拉滿”?,F(xiàn)實完全不是這樣。HarmonyOS 設(shè)備通常有 4 核、8 核甚至更多核心但你的任務(wù)池線程數(shù)量是有限的而且線程切換是有代價的。當(dāng)一個 CPU 核心在多個線程之間來回切換時每次切換都要保存和恢復(fù)上下文這些開銷不僅不產(chǎn)出任何計算結(jié)果還會把 CPU 時間片切碎。我用一個簡單模型來理解這事假設(shè)單個分片計算需要 10 毫秒數(shù)據(jù)分成 40 個分片。如果串行執(zhí)行總耗時大約 400 毫秒。如果并發(fā)數(shù)設(shè)為 4理想情況下總耗時約 100 毫秒但實際因為線程創(chuàng)建、調(diào)度、等待可能在 120 到 150 毫秒之間。如果并發(fā)數(shù)設(shè)為 40理論上只有 10 毫秒但線程切換成本會讓實際耗時飆升到 300 毫秒以上而且會擠壓其他應(yīng)用和系統(tǒng)服務(wù)的線程。并發(fā)收益的曲線是倒 U 型不是單調(diào)遞增。5.2 如何選擇合理的并發(fā)數(shù)我的經(jīng)驗法則是并發(fā)數(shù)接近但不超過設(shè)備核心數(shù)。更具體地說CPU 密集任務(wù)建議設(shè)置為設(shè)備可用核心數(shù) - 1留出一個核心給主線程和系統(tǒng)響應(yīng)使用。如果設(shè)備有 8 核那么你設(shè)置 7 個并發(fā)計算分片是比較穩(wěn)妥的。HarmonyOS 的 API 里你可以獲取系統(tǒng)的 CPU 核心數(shù)量信息。在高版本系統(tǒng)中任務(wù)池本身也會根據(jù)設(shè)備負(fù)載情況調(diào)整并發(fā)度但作為應(yīng)用開發(fā)者我仍然建議手動控制并發(fā)數(shù)特別是當(dāng)你的計算任務(wù)分片特別多時要避免一次性把所有分片都塞進任務(wù)池。一個實用的做法是采用固定窗口的調(diào)度維護一個正在執(zhí)行的任務(wù)列表當(dāng)任務(wù)完成一個才從待處理隊列中取出下一個分片投遞。這樣既保證了并發(fā)數(shù)不超過上限又保證了任務(wù)隊列的吞吐穩(wěn)定。5.3 實測數(shù)據(jù)對比不同并發(fā)策略在真實設(shè)備上的表現(xiàn)我在一臺 8 核真機上對同樣的 1000 萬分片統(tǒng)計任務(wù)做了三組實驗策略并發(fā)策略總耗時主線程掉幀數(shù)說明串行子線程13.2 秒0UI 完全流暢但總耗時最長用戶等待久全量并射40 個分片全部同時投遞1.9 秒0速度快但 CPU 占用飆到 80% 以上發(fā)熱明顯窗口限流固定 4 并發(fā) 隊列消費2.3 秒0速度適中CPU 占用約 45%發(fā)熱可控全量并射看起來總耗時最短但代價是整機資源被占滿。如果你在頁面上還播放了視頻或者有動畫其他線程很可能搶不到資源體驗反而下降。我最終采用的是窗口限流方案因為它在“計算速度”和“系統(tǒng)穩(wěn)定性”之間取得了更好的平衡。5.4 優(yōu)先級與資源感知讓系統(tǒng)知道你在做什么除了并發(fā)數(shù)任務(wù)優(yōu)先級也是影響 CPU 計算體驗的重要參數(shù)。HarmonyOS 任務(wù)池允許為任務(wù)設(shè)置優(yōu)先級高優(yōu)先級任務(wù)會優(yōu)先被調(diào)度。這里我的建議是前臺可見的計算任務(wù)可以適當(dāng)提高優(yōu)先級但不要讓所有分片都是高優(yōu)先級否則會擠占系統(tǒng)渲染和事件處理。后臺預(yù)加載類計算務(wù)必使用默認(rèn)或低于默認(rèn)的優(yōu)先級避免影響用戶當(dāng)前的操作。還有一個隱藏點要感知設(shè)備功耗狀態(tài)。設(shè)備在低電量模式下會限制 CPU 頻率此時 CPU 計算會變得很慢。如果你的應(yīng)用在低電量下堅持全速并發(fā)不僅算不快還可能加劇設(shè)備發(fā)熱和耗電。一個負(fù)責(zé)任的做法是在低電量模式下主動降低并發(fā)數(shù)或者延遲非關(guān)鍵計算到電量充足時再執(zhí)行。6. 優(yōu)化后的實際效果與幾個值得注意的取舍6.1 最終方案的落地形態(tài)經(jīng)過前面幾輪調(diào)整我最終使用的方案是 Future 負(fù)責(zé)流程編排、Callback 負(fù)責(zé)進度反饋、任務(wù)池窗口限流負(fù)責(zé)并發(fā)調(diào)度。它們各司其職互不干擾。可能有人覺得兩套模型混用會導(dǎo)致代碼混亂但在我看來只要約定清楚“Future 管流程Callback 管事件”反而比單一模型更清晰。落地后的頁面交互變成這樣用戶點擊處理按鈕按鈕變成可取消狀態(tài)進度條開始平滑增長每個階段顯示對應(yīng)的文字提示“正在排序分片”“正在統(tǒng)計特征”“正在生成結(jié)果”。整個計算期間用戶還可以正常上下滾動頁面、點擊其他按鈕不再有卡頓感。6.2 我在調(diào)試中遇到的兩個隱蔽問題問題一子線程和主線程間傳遞大數(shù)據(jù)對象的拷貝成本。最開始我把整個數(shù)組直接傳給子線程每個分片在子線程里處理完后再把結(jié)果傳回主線程。數(shù)據(jù)量大的時候線程間通信的序列化和反序列化開銷非??捎^甚至抵消了并行收益。后來的做法是在任務(wù)池中直接完成大部分聚合運算只把最終幾個數(shù)字傳回主線程而不是傳遞整個中間結(jié)果集。這提醒我設(shè)計計算任務(wù)時要盡量減少跨線程的數(shù)據(jù)傳輸。問題二異常的靜默失敗。子線程中的計算如果拋出異常Future 會在 Promise 中變成 rejected 狀態(tài)但如果你忘了在鏈尾捕獲錯誤日志可能一閃而過用戶看到的是“什么也沒發(fā)生”。我在調(diào)試某個統(tǒng)計頁面時就遇到過數(shù)據(jù)里混入了非法值在子線程里拋了異常結(jié)果真實原因被誤判成了“算法太慢”。最后我養(yǎng)成了習(xí)慣所有投遞到任務(wù)池的函數(shù)入口和出口都包上 try/catch并保證任何錯誤都會回調(diào)到主線程打印日志。6.3 給同樣在做 CPU 計算優(yōu)化的人一點個人體會如果你正要著手優(yōu)化 HarmonyOS 應(yīng)用里的 CPU 密集計算我的建議是先量化再改造。在動手寫異步代碼之前先明確計算耗時的分布、主線程的影響范圍、以及用戶可接受的等待時長。然后按照“先換線程再定風(fēng)格后調(diào)并發(fā)”的順序推進。換線程解決卡頓Future 或 Callback 解決協(xié)作方式并發(fā)調(diào)節(jié)解決效率。每一步都有明確的驗證指標(biāo)不要一口氣全改完再測否則出了問題你根本不知道是哪一環(huán)導(dǎo)致的。另外我特別建議你在真機上驗證而不要在模擬器里做性能判斷。CPU 核心數(shù)、調(diào)度策略、功耗管理在真機和模擬器上的差異很大很多在模擬器上表現(xiàn)完美的方案到了真機才發(fā)現(xiàn)并發(fā)數(shù)設(shè)置得不合理。調(diào)試的時候可以把設(shè)備連上性能分析工具同時看 CPU 占用率、線程數(shù)量和主線程幀率這些數(shù)據(jù)比任何斷言都更有說服力。