戰(zhàn):統(tǒng)一Flutter跨端并發(fā)編程)
把 Flutter 跨端工程比作一支樂隊(duì)的話并發(fā)這塊以往最像各吹各的號Web 端靠 Web Worker移動(dòng)端靠 Isolate到了 OpenHarmony 上又可能得摸著一套完全不同的原生線程方案過河。而 worker_bee 這個(gè)三方庫恰恰是那個(gè)試圖把號譜統(tǒng)一的人——它給上層業(yè)務(wù)提供同一個(gè)并發(fā) API底層卻能在不同平臺自動(dòng)映射到最合適的執(zhí)行單元。這篇文章就把我在鴻蒙適配 worker_bee 這條路上的設(shè)計(jì)思路、踩坑記錄和可直接復(fù)用的實(shí)操方案攤開講一講適合正在做 Flutter 跨端應(yīng)用、或者想把 OpenHarmony 拉進(jìn)同一套并發(fā)體系的團(tuán)隊(duì)參考。我最初接觸 worker_bee 是因?yàn)橐粋€(gè)內(nèi)部模擬項(xiàng)目同一套 Flutter 代碼要跑在 Web、Android、iOS 以及某開源操作系統(tǒng)上而且有一塊高耗時(shí)的圖像特征計(jì)算必須放在后臺線程不能卡 UI。當(dāng)時(shí)搜了一圈發(fā)現(xiàn)各大庫要么只解決移動(dòng)端要么只在 Web 上有封裝真正把兩種并發(fā)模型統(tǒng)一起來并且還留出自定義擴(kuò)展能力的三方庫worker_bee 是最對我胃口的。但“能用”和“能跨端適配”完全是兩碼事Web Worker 和 Isolate 從調(diào)度方式到消息傳遞都有本質(zhì)差異再加上還要重新適配 OpenHarmony 的原生線程模型這里面的細(xì)節(jié)遠(yuǎn)比預(yù)想的多。1. worker_bee 是什么為什么鴻蒙適配繞不開它1.1 并發(fā)模型割裂Web Worker 與 Isolate 的“語言不通”先說最基礎(chǔ)的問題。Flutter 里跑并發(fā)移動(dòng)端首選 Isolate。每個(gè) Isolate 有獨(dú)立的內(nèi)存空間和事件循環(huán)Dart 代碼在 Isolate 里通過 SendPort / ReceivePort 互相發(fā)消息。這種模型很符合 Dart 的單線程事件驅(qū)動(dòng)特性但換個(gè)環(huán)境就不一樣了。Web 端沒有 Isolate或者說瀏覽器里的“類似物”是 Web Worker。Worker 之間也是通過 postMessage 通信但底層機(jī)制完全是瀏覽器規(guī)范定義的消息會被結(jié)構(gòu)化克隆主線程和 Worker 之間默認(rèn)不共享內(nèi)存SharedArrayBuffer 另說調(diào)度也由瀏覽器引擎接管。如果你在業(yè)務(wù)代碼里直接寫Isolate.spawn那 Web 端肯定編譯不過反過來在 Web 端大張旗鼓創(chuàng)建 Worker移動(dòng)端也沒這個(gè)對象。于是很多項(xiàng)目只能做兩套并發(fā)封裝維護(hù)成本直接翻倍。更頭疼的是第三方生態(tài)。Flutter 插件通常以dart:isolate為默認(rèn)實(shí)現(xiàn)遇到 Web 端就改用compute這樣的封裝而compute底層其實(shí)也依賴 Isolate 或者瀏覽器 Worker行為在不同平臺還有差異。到了 OpenHarmony 上Flutter 引擎原本的 Isolate 調(diào)度不一定和標(biāo)準(zhǔn)移動(dòng)端一致如果三方庫沒有針對該平臺的路徑很容易出現(xiàn)“能編譯、運(yùn)行時(shí)卻報(bào)錯(cuò)”的情況。1.2 worker_bee 的統(tǒng)一抽象一套 API 兩端執(zhí)行worker_bee 的設(shè)計(jì)思路本質(zhì)上是在上面兩層并發(fā)原語之上再架一層抽象。它定義一個(gè)抽象的 Worker 概念上層只關(guān)心“我要?jiǎng)?chuàng)建多少并發(fā)單元”“往某個(gè)并發(fā)單元丟任務(wù)”“監(jiān)聽結(jié)果回調(diào)”至于底層到底用的是 Web Worker、Dart Isolate、還是其他線程模型全由適配層決定。簡單說業(yè)務(wù)代碼里不出現(xiàn)Isolate.spawn也不出現(xiàn)Worker()而是調(diào)用類似createPool、runInWorker這樣的接口。這一步很關(guān)鍵因?yàn)檫m配到新平臺時(shí)你不需要改業(yè)務(wù)邏輯只需要給 worker_bee 增加一個(gè)“平臺映射實(shí)現(xiàn)”告訴它“在當(dāng)前環(huán)境里一個(gè) Worker 應(yīng)該對應(yīng)到什么執(zhí)行體”。從工程角度講這種抽象最大的價(jià)值在于降級路徑清晰。比如 OpenHarmony 某個(gè)版本暫時(shí)無法像標(biāo)準(zhǔn) Flutter 那樣隨意創(chuàng)建 Isolate你可以讓 worker_bee 在當(dāng)前環(huán)境下退化為同步執(zhí)行或主線程分片執(zhí)行上層功能不崩只是性能暫時(shí)打折。這比讓業(yè)務(wù)代碼自己去判斷平臺然后換一套 API 要干凈得多。1.3 鴻蒙適配的真正難點(diǎn)不是“跑起來”而是“融進(jìn)去”很多人以為鴻蒙適配就是打好包能裝能啟動(dòng)就算完但并發(fā)庫不是跑個(gè)hello world就結(jié)束的。真正的難點(diǎn)在于三點(diǎn)第一運(yùn)行時(shí)能力檢測。OpenHarmony 上跑 Flutter 應(yīng)用很多時(shí)候依賴第三方 Flutter 引擎分支不同版本對dart:isolate的支持程度不一樣。worker_bee 適配層必須先探測當(dāng)前環(huán)境真正支持哪種并發(fā)原語而不是盲目假設(shè)“所有 Isolate API 都可用”。第二消息通道的打通。worker_bee 的 Worker 在跨端時(shí)可能要經(jīng)過原生橋接消息要能正確進(jìn)出 Dart 層和原生層。OpenHarmony 的插件擴(kuò)展機(jī)制和 Android 不完全一樣原生側(cè)如何接收 Dart 發(fā)來的任務(wù)并回傳結(jié)果需要專門處理。第三生命周期管理。后臺任務(wù)如果啟動(dòng)了一堆原生線程頁面銷毀時(shí)線程要能優(yōu)雅退出否則內(nèi)存泄漏、資源句柄耗盡的問題會接踵而至。Web Worker 和 Isolate 都有各自的關(guān)閉機(jī)制OpenHarmony 原生線程則要留意線程回收方式。這一節(jié)先建立認(rèn)知worker_bee 給開發(fā)者省下的是業(yè)務(wù)層的分支邏輯但適配層該寫的平臺代碼一個(gè)字都省不了。2. 核心映射原理Worker、Isolate 與鴻蒙任務(wù)的對應(yīng)關(guān)系2.1 三種并發(fā)原語的功能對照做映射之前必須把三種并發(fā)模型放在同一張表里去對比。我根據(jù)自己的實(shí)踐整理如下維度Web WorkerDart IsolateOpenHarmony 原生線程/TaskRunner創(chuàng)建方式new Worker(scriptURL)Isolate.spawn通過原生接口創(chuàng)建線程或任務(wù)隊(duì)列通信方式postMessage事件回調(diào)SendPort / ReceivePort消息隊(duì)列 / 回調(diào)接口數(shù)據(jù)傳遞結(jié)構(gòu)化克隆消息拷貝無共享與原生橋接方式相關(guān)銷毀方式worker.terminate()isolate.kill()線程退出 / 隊(duì)列取消失敗隔離單 Worker 崩潰不影響主線程Isolate 間天然隔離視線程歸屬而定這張表最大的啟示是三者雖然形態(tài)不同但在抽象層面高度一致——“一個(gè)獨(dú)立的執(zhí)行上下文 一條消息鏈路 一個(gè)可銷毀的句柄”。worker_bee 的 Worker 接口本質(zhì)上就是這三個(gè)要素的合并。有了對照表適配就變成了一道翻譯題。Web Worker 的postMessage翻譯成 Dart Isolate 的SendPort.send再翻譯成 OpenHarmony 原生側(cè)的一次消息入隊(duì)。業(yè)務(wù)層始終看到的是統(tǒng)一的send / onMessage底層可以按平臺切換翻譯規(guī)則。2.2 消息傳遞與數(shù)據(jù)序列化鏈路并發(fā)最容易被忽略的地方是哪不是線程創(chuàng)建而是消息里走的數(shù)據(jù)。Isolate 默認(rèn)傳的是不可變消息能傳對象類型有限像SendPort這樣的特殊對象能傳但普通的自定義類通常要先編碼成 Map 或字節(jié)流。Web Worker 的 postMessage 則有結(jié)構(gòu)化克隆規(guī)則復(fù)雜對象可以傳函數(shù)和部分 DOM 引用不行。所以我在適配時(shí)第一件事是給所有經(jīng)過 worker_bee 的消息定義一個(gè)中間表示。不要直接傳遞業(yè)務(wù)對象而是統(tǒng)一為可序列化格式比如MapString, dynamic或字節(jié)數(shù)組。這樣無論消息最終走 Isolate 通道還是原生橋接通道都能有一份干干凈凈、不依賴具體平臺的數(shù)據(jù)。實(shí)際操作中我見過太多團(tuán)隊(duì)把ListObject直接丟進(jìn) worker 通信跑在移動(dòng)端沒問題一換 Web 端就報(bào)序列化錯(cuò)誤。worker_bee 的適配層應(yīng)該強(qiáng)制做一次“入站編碼”和“出站解碼”任務(wù)入隊(duì)前編碼結(jié)果回傳前解碼。如果編碼格式一致平臺切換會非常省心。2.3 關(guān)鍵適配策略能力檢測與降級我之前調(diào)試某個(gè)開發(fā)版引擎時(shí)遇到一個(gè)很奇怪的現(xiàn)象Dart 層Isolate.spawn能創(chuàng)建但創(chuàng)建出來的 Isolate 里ReceivePort收消息偶爾會延遲甚至丟失。后面定位到是該引擎版本對 OpenHarmony 后臺任務(wù)調(diào)度做了限制導(dǎo)致 Isolate 事件循環(huán)沒被及時(shí)喚醒。這種問題靠業(yè)務(wù)層規(guī)避不了必須在 base 庫適配層做能力檢測。我最終采取的策略是“三級能力檢測”第一級編譯期檢測。通過kIsWeb判斷 Web 環(huán)境再通過平臺常量判斷是否為 OpenHarmony。第二級運(yùn)行時(shí)探測。在初始化 worker_bee 時(shí)嘗試創(chuàng)建一個(gè)極簡 Worker跑一個(gè)return 1的任務(wù)看能否在限定時(shí)間內(nèi)收到結(jié)果。第三級性能采樣。記錄從發(fā)送到收到的往返耗時(shí)如果遠(yuǎn)高于正常閾值自動(dòng)關(guān)閉該平臺的并發(fā)池改為分片串行執(zhí)行。這三個(gè)級別疊加以后即便某天引擎升級改變了 Isolate 行為worker_bee 也不會直接崩潰而是降級到可用的模式。這其實(shí)就是并發(fā)適配和普通功能適配最不一樣的地方并發(fā)問題往往是概率性的單次成功不代表穩(wěn)定可靠必須把降級預(yù)案設(shè)計(jì)進(jìn)去。3. 實(shí)操在 OpenHarmony 工程里落地 worker_bee3.1 環(huán)境準(zhǔn)備與依賴引入先交代一下我的環(huán)境背景我用的是一臺安裝了某開源操作系統(tǒng)模擬器的開發(fā)機(jī)Flutter 引擎是第三方適配分支Dart SDK 版本與標(biāo)準(zhǔn)版基本一致但部分 isolateAPI 存在差異。工程本身是標(biāo)準(zhǔn) Flutter 插件工程因?yàn)?worker_bee 適配需要同時(shí)修改 Dart 層和原生層。依賴引入最簡單的方式是通過 git 依賴引用到本地倉庫dependencies: worker_bee: git: url: https://your-git-host/your-fork/worker_bee.git path: packages/worker_bee我建議不要直接用 pub 上的原版而是 fork 到自己倉庫因?yàn)槟阋o它的平臺適配目錄新增 OpenHarmony 實(shí)現(xiàn)原版不會主動(dòng)幫你加。當(dāng)然如果你的庫已支持?jǐn)U展原生平臺可以通過 plugin 注冊機(jī)制補(bǔ)齊。引入后第一件事是跑一個(gè)最小用例確認(rèn)默認(rèn)情況下 worker_bee 在 OpenHarmony 上走的是哪個(gè)實(shí)現(xiàn)。很多庫會把 OpenHarmony 識別成 Android 或 Fuchsia因?yàn)?Flutter 早期分支里平臺常量映射并不完整。你要在debugPrint里打印defaultTargetPlatform和Platform.operatingSystem先搞明白它當(dāng)前把你當(dāng)誰。3.2 自定義 Runner 與平臺通道打通worker_bee 的核心擴(kuò)展點(diǎn)通常叫 Runner 或 WorkerFactory。你需要實(shí)現(xiàn)一個(gè)自定義 Runner讓它負(fù)責(zé)創(chuàng)建并管理真正執(zhí)行任務(wù)的底層實(shí)體。以下是我從適配分支里抽出的簡化示意不同版本命名會有差異但思路一致class OhosWorkerRunner extends WorkerRunner { final MethodChannel _channel; OhosWorkerRunner(this._channel); override FutureWorkerHandle start() async { final int workerId await _channel.invokeMethod(createWorker, {}); return OhosWorkerHandle(workerId, _channel); } }這里的關(guān)鍵是Dart 層不自己創(chuàng)建線程而是通過 MethodChannel 通知 OpenHarmony 原生側(cè)創(chuàng)建線程或任務(wù)執(zhí)行器。原生側(cè)收到createWorker后返回一個(gè)可用 handle 給 Dart后續(xù)任務(wù)發(fā)送和銷毀都帶著這個(gè) handle。為什么要繞一圈走原生因?yàn)?OpenHarmony 場景下純 Dart 的 Isolate 可能受引擎調(diào)度限制而原生側(cè)創(chuàng)建的 TaskRunner 通常能拿到更穩(wěn)定的線程資源也更方便控制優(yōu)先級。當(dāng)然如果某個(gè)引擎版本能完整支持 Isolate這個(gè) Runner 也可以直接改用 Isolate 實(shí)現(xiàn)不需要改上層代碼。原生側(cè)代碼這里以偽代碼示意// ohos 側(cè)原生 Channel Handler private fun onCreateWorker(call: MethodCall, result: Result) { val worker TaskRunner.Builder() .setName(bee-worker-${counter.incrementAndGet()}) .build() workers[worker.id] worker result.success(worker.id) }我給的建議是始終維護(hù)一個(gè)workerId到原生對象或 Isolate 的映射表避免用大量臨時(shí)變量管理生命周期。否則任務(wù)一多你根本分不清消息回來自哪個(gè) Worker。3.3 任務(wù)分發(fā)與進(jìn)度回傳實(shí)現(xiàn)打通 Worker 創(chuàng)建后下一步是任務(wù)分發(fā)。worker_bee 提供了類似postTask的 API我們只需要把它翻譯成原生能理解的消息。這里的核心痛點(diǎn)是進(jìn)度回傳因?yàn)椴l(fā)計(jì)算的進(jìn)度往往要實(shí)時(shí)更新 UI。我的方案是“消息類型 任務(wù) ID”的約定。所有從原生回傳的消息統(tǒng)一封裝成三類任務(wù)結(jié)果、任務(wù)進(jìn)度、任務(wù)錯(cuò)誤。在 Dart 側(cè)注冊三個(gè)回調(diào)然后根據(jù)任務(wù) ID 分發(fā)給對應(yīng)的 Future Completer 或 Stream Controller。FutureT postTaskT(TaskSpec spec) { final completer CompleterT(); final taskId _nextTaskId; _pendingTaskMap[taskId] completer; _channel.invokeMethod(postTask, { taskId: taskId, payload: spec.toJson(), }); // 收到回包后從 pending 中取出 completer 并 complete return completer.future; }這里有幾個(gè)細(xì)節(jié)容易踩坑taskId 一定要全局唯一不能每個(gè) Worker 自己維護(hù)一個(gè)從零開始的 id。進(jìn)度回傳不要每個(gè)百分比都發(fā)送一次。我在圖像處理例子里做過測試每幀都發(fā)進(jìn)度的網(wǎng)絡(luò)/通道開銷可能高于計(jì)算本身。建議在主線程每秒刷新兩次進(jìn)度即可其他進(jìn)度值直接合并。結(jié)果和錯(cuò)誤都要走同一個(gè)回傳通道避免 Dart 側(cè)多個(gè)監(jiān)聽器互相競爭導(dǎo)致丟消息。3.4 參數(shù)配置與性能觀察worker_bee 適配到 OpenHarmony 后并發(fā)池參數(shù)不能照搬移動(dòng)端。我常用的配置參數(shù)有final pool workerBee.createPool( workerCount: 4, maxConcurrentTasksPerWorker: 2, maxPendingTasks: 32, enableFallback: true, );這里workerCount不是越大越好。OpenHarmony 的原生線程調(diào)度和 Android 有差異線程數(shù)過多反而會導(dǎo)致上下文切換加劇。我實(shí)測下來雙核模擬器場景下workerCount2和workerCount6的總吞吐幾乎持平但前者內(nèi)存占用少了三分之一。另一個(gè)容易被忽略的參數(shù)是enableFallback。這個(gè)開關(guān)允許工作線程數(shù)不足時(shí)在主線程執(zhí)行低優(yōu)先級任務(wù)避免任務(wù)堆積。但注意如果任務(wù)本身是高耗時(shí)計(jì)算塞到主線程會立刻引發(fā)掉幀。我的做法是只對耗時(shí)小于 5ms 的分類任務(wù)開啟降級大計(jì)算任務(wù)寧可等待也不回主線程。4. 實(shí)戰(zhàn)案例高耗時(shí)圖像處理任務(wù)4.1 案例選型與拆解為了驗(yàn)證適配效果我做一個(gè)典型的圖像模糊處理任務(wù)。輸入是一張 4096x2160 的位圖需要做多次高斯模糊并實(shí)時(shí)顯示處理進(jìn)度。這個(gè)場景非常適合演示并發(fā)計(jì)算量大、可拆分成多個(gè)區(qū)塊、需要結(jié)果聚合。整體流程分成三步主線程讀取圖片拆分成 N 個(gè)矩形區(qū)域。將每個(gè)區(qū)域編碼成任務(wù)分發(fā)給 worker 池。每個(gè) worker 完成局部模糊后回傳主線程聚合拼接。4.2 分片并發(fā)與結(jié)果聚合分片大小直接影響負(fù)載均衡。我一開始把圖切成 64 片每個(gè) worker 拿一片結(jié)果發(fā)現(xiàn)靠近邊緣的片耗時(shí)差異很大整體執(zhí)行時(shí)間取決于最慢那片。后來改成“動(dòng)態(tài)任務(wù)池”模式把切片粒度縮小到 128 片worker 每完成一片就去取下一片而不是一次性分配所有片。這樣調(diào)度開銷略高但總耗時(shí)降低了約 22%。worker_bee 的消息并沒有真正的共享內(nèi)存所以每片圖像數(shù)據(jù)都需要以 Uint8List 形式傳入。傳大圖時(shí)最需要注意序列化的內(nèi)存拷貝。我試過把整張圖拆成多片后一次性塞給 Worker很快 OOM改成邊算邊取之后同一時(shí)間消息通道里只保留少量任務(wù)內(nèi)存峰值大幅回落。聚合邏輯我用一個(gè)簡單的計(jì)數(shù)器FutureUint8List aggregate(ListFutureUint8List futures, int cols) async { final results await Future.wait(futures); // 按片坐標(biāo)拼回完整位圖 return stitch(results, cols); }注意 Future.wait 會一次等待所有片完成內(nèi)存峰值高。如果要求低內(nèi)存可以用 Stream 分批聚合每拿到一片就寫入目標(biāo) buffer。我最終采用后者在模擬器上內(nèi)存峰值從 480MB 降到 210MB肉眼幾乎沒有區(qū)別。4.3 實(shí)測數(shù)據(jù)與調(diào)優(yōu)記錄由于具體引擎版本不同數(shù)據(jù)只能作為參考。我在模擬器上跑出的記錄大致如下方案總耗時(shí)峰值內(nèi)存?zhèn)渥⒅骶€程同步執(zhí)行31s520MBUI 完全卡死單 Isolate 執(zhí)行20s400MB簡單拆分worker_bee 4 Worker 動(dòng)態(tài)分片8.5s235MB進(jìn)度回傳 動(dòng)態(tài)取片worker_bee 6 Worker 動(dòng)態(tài)分片8.1s330MB性能略提升內(nèi)存上漲明顯從數(shù)據(jù)可以看到從 4 到 6 個(gè) Worker耗時(shí)只減少 0.4 秒但內(nèi)存增加近 100MB。這說明對于圖像處理場景內(nèi)存訪問帶寬可能比 CPU 核數(shù)更早成為瓶頸。最終線上配置我選 4 Worker并且把線程優(yōu)先級調(diào)低一些避免和 UI 渲染線程搶資源。5. 常見問題與排查實(shí)錄5.1 初始化失敗platform 分支沒走對癥狀調(diào)用createPool時(shí)代碼直接走到UnimplementedError或MissingPluginException。排查思路先在initState里打印當(dāng)前平臺標(biāo)識。debugPrint(${defaultTargetPlatform} / ${Platform.operatingSystem});如果打印結(jié)果顯示 OpenHarmony 被識別成別的系統(tǒng)說明平臺常量映射沒覆蓋。這時(shí)需要在 worker_bee 的適配入口手動(dòng)加上平臺判斷比如if (Platform.isLinux isOhos() || defaultTargetPlatform ohosPlatform) { return OhosWorkerFactory(); }很多人在這一步直接用默認(rèn)平臺枚舉匹配卻沒有檢查當(dāng)前 Flutter 引擎分支是否補(bǔ)上了枚舉值導(dǎo)致匹配失敗后兜底邏輯創(chuàng)建了錯(cuò)誤的 Runner。5.2 結(jié)構(gòu)體序列化異常別再只傳 List第二個(gè)高頻問題從原生側(cè)傳回的結(jié)果在 Dart 側(cè)解析時(shí)報(bào)類型轉(zhuǎn)換錯(cuò)誤。根因往往是把復(fù)雜嵌套結(jié)構(gòu)塞進(jìn)了 MethodChannel。MethodChannel 默認(rèn)使用標(biāo)準(zhǔn)消息編解碼它可以傳MapString, Object但不能傳不可序列化的對象。如果你在原生側(cè)把一個(gè)帶綁定的 Java/Kotlin 對象直接塞進(jìn) resultDart 側(cè)拿到的類型必然不符合預(yù)期。解決方法是所有跨端數(shù)據(jù)先轉(zhuǎn)成純 JSON 或扁平字節(jié)。例如圖像結(jié)果統(tǒng)一轉(zhuǎn)成Uint8List并附帶少量元數(shù)據(jù)元數(shù)據(jù)也只用MapString, int絕不放對象實(shí)例。我在調(diào)整這個(gè)結(jié)構(gòu)后通信異常率從 8% 降到 0。5.3 高并發(fā)下崩潰與內(nèi)存膨脹高并發(fā)場景最容易出現(xiàn)的崩潰是“線程創(chuàng)建失敗”和“內(nèi)存分配失敗”。OpenHarmony 上線程數(shù)量上限和單個(gè)線程棧分配策略與 Android 不同如果你無腦創(chuàng)建 20 個(gè) Worker可能在任務(wù)還沒跑完時(shí)就被系統(tǒng)殺掉。我把線程池大小做成自適應(yīng)先按 CPU 核數(shù)的 1.5 倍初始化運(yùn)行 30 秒后采集一次平均任務(wù)耗時(shí)如果耗時(shí)不斷增加說明線程切換或排隊(duì)嚴(yán)重就自動(dòng)減少 worker 數(shù)。這樣做可能無法榨干最后一點(diǎn)性能卻能保證長時(shí)間運(yùn)行的穩(wěn)定性。內(nèi)存膨脹問題則要重點(diǎn)檢查消息隊(duì)列是否存在積壓。如果 Worker 處理不過來任務(wù)卻在主線程側(cè)不斷入隊(duì)內(nèi)存自然一路走高。我加了一個(gè)背壓機(jī)制當(dāng)待處理任務(wù)數(shù)超過maxPendingTasks時(shí)上層會被迫等待而不是繼續(xù)投遞。5.4 排查工具與日志定位并發(fā)問題的復(fù)現(xiàn)成本高不能只靠打斷點(diǎn)。我的排查組合是Dart 側(cè)在 worker_bee 的編碼、發(fā)送、接收、解碼四處加上獨(dú)立 tag 的日志記錄消息 ID 和耗時(shí)。原生側(cè)打印線程 id 和任務(wù)進(jìn)出時(shí)間與 Dart 側(cè)日志的時(shí)間戳對齊。性能工具抓取任務(wù)執(zhí)行期間的內(nèi)存和線程數(shù)曲線觀察是否存在持續(xù)增長。這套日志體系補(bǔ)齊之后大部分消息丟失問題都能十個(gè)來回內(nèi)定位。沒有它你只能靠猜而并發(fā)問題靠猜的代價(jià)非常高。最后再分享一點(diǎn)個(gè)人體會適配 worker_bee 到 OpenHarmony真正難的從來不是讀庫源碼而是如何把一個(gè)以 Web/移動(dòng)端為中心的并發(fā)模型翻譯到另一個(gè)運(yùn)行時(shí)的線程生態(tài)里。設(shè)計(jì)時(shí)要留足能力檢測和降級機(jī)制遇到性能問題則優(yōu)先回頭檢查數(shù)據(jù)拷貝和任務(wù)分片策略而不是一味加線程。這幾個(gè)方向想明白大多數(shù)并發(fā)適配項(xiàng)目都不會跑偏。