无需API调用:Chandra本地AI聊天室搭建全流程解析
æ–‡ç« ç›®å½•å‰�言一ã€� ç�†è§£ Actor 模å�‹è¿™ä¸€åº§åº§å¤å²›äºŒã€� TaskPooléš�å�«éš�到的特ç§�部队三ã€� Worker常驻å��å�°çš„专è�Œç®¡å®¶å››ã€� 通信机制ä»�“拷è´�â€�到“转移â€�五ã€�å®�战示例å…ã€� 总结ä¸�å®�战å‰�言在移动应用开å�‘䏿µ�畅度就是生命线。用户的手指在å±�幕上滑动的æ¯�一毫秒系统都需è¦�在 16ms 内完æˆ�一帧画é�¢çš„æ¸²æŸ“。如æ�œæˆ‘们的主线程也就是 UI 线程被任何耗时的æ“�ä½œå µå¡�哪怕å�ªæ˜¯è¯»å�–一个ç¨�大的文件或者进行一次å¤�æ�‚的数å¦è¿�算界é�¢å°±ä¼šç«‹åˆ»æ�‰å¸§ç”šè‡³å®Œå…¨å�¡æ»ã€‚è¿™ç§�体验对äº�用户æ�¥è¯´æ˜¯ç�¾é𾿀§çš„。在 Java 或 C çš„ä¼ ç»Ÿå¼€å�‘䏿ˆ‘ä»¬ä¹ æƒ¯äº†ç›´æ�¥new Threadæˆ–è€…ä½¿ç”¨çº¿ç¨‹æ± æ�¥åˆ†æ‹…任务但在鸿蒙 HarmonyOS 6 (API
çš„ ArkTS 引æ“�ä¸å¹¶å�‘模å�‹å�‘ç”Ÿäº†ä¸€äº›æ ¹æœ¬æ€§çš„å�˜åŒ–。ArkTS 采用了Actor å¹¶å�‘模å�‹è¿™æ„�味ç�€çº¿ç¨‹ä¹‹é—´æ²¡æœ‰å…±äº«å†…å˜ä¹Ÿæ²¡æœ‰é‚£ä»¤äººå¤´ç§ƒçš„é”�机制。今天我们就æ�¥è�Šè�Šå¦‚何在这个新模å�‹ä¸‹åˆ©ç”¨TaskPoolå’ŒWorker优雅地处ç�†è€—时任务把主线程的å®�贵资æº�还给 UI 渲染。一ã€� ç�†è§£ Actor 模å�‹è¿™ä¸€åº§åº§å¤å²›è¦�æ�Œæ�¡é¸¿è’™çš„å¹¶å�‘首先得æ‰è½¬ä¸€ä¸ªè§‚念线程之间ä¸�å†�能éš�æ„�访问å�Œä¸€ä¸ªå�˜é‡�äº†ã€‚åœ¨ä¼ ç»Ÿçš„å…±äº«å†…å˜æ¨¡å�‹ä¸å¤šä¸ªçº¿ç¨‹å�¯ä»¥å�Œæ—¶è¯»å†™å�Œä¸€ä¸ªå…¨å±€å�˜é‡�ä¸ºäº†é˜²æ¢æ•°æ�®é”™ä¹±æˆ‘们ä¸�å¾—ä¸�引入å�„ç§�é”�Lockå’Œå�Œæ¥æœºåˆ¶è¿™å¾€å¾€æ˜¯æ»é”�å’Œç«�æ€�æ�¡ä»¶çš„æ¸©åºŠã€‚而 ArkTS 采用的 Actor 模å�‹å°†æ¯�ä¸€ä¸ªçº¿ç¨‹æ— è®ºæ˜¯ä¸»çº¿ç¨‹ã€�TaskPool 线程还是 Worker 线程都视为一个独立的 Actorã€‚ä½ å�¯ä»¥æŠŠå®ƒä»¬æƒ³è±¡æˆ�一个个独立的“房间â€�æ¯�个房间里有自己的内å˜ç©ºé—´ã€‚A 房间的人想è¦�把数æ�®ç»™ B 房间ä¸�能直æ�¥æŠŠæ‰‹ä¼¸è¿‡å�»è€Œå¿…须通过“å�‘消æ�¯â€�的方å¼�把数æ�®å¤�åˆ¶ä¸€ä»½ä¼ é€’è¿‡å�»ã€‚è¿™ç§�è®¾è®¡çš„æœ€å¤§å¥½å¤„å°±æ˜¯å¤©ç„¶æ— é”�彻底æ�œç»�了数æ�®ç«�争带æ�¥çš„ Bugæ��大地æ��高了系统的稳定性。但代价就是我们在设计代ç �时必须时刻关注数æ�®çš„ä¼ é€’æˆ�本。二ã€� TaskPooléš�å�«éš�到的特ç§�部队在 API 20 ä¸é¸¿è’™å®˜æ–¹å¼ºçƒˆæ�¨è��首选TaskPoolä»»åŠ¡æ± æ�¥å¤„ç�†å¹¶å�‘ä»»åŠ¡ã€‚ä½ å�¯ä»¥æŠŠå®ƒçœ‹ä½œæ˜¯ä¸€ä¸ªæ™ºèƒ½çš„网约车调度平å�°ã€‚å½“ä½ æœ‰ä¸€ä¸ªè€—æ—¶ä»»åŠ¡æ¯”å¦‚å›¾ç‰‡æ»¤é•œå¤„ç�†æ—¶ä½ ä¸�需è¦�自己å�»åˆ›å»ºçº¿ç¨‹ä¹Ÿä¸�需è¦�关心线程的生命周期å�ªéœ€è¦�把任务包装好扔给 TaskPoolã€‚ç³»ç»Ÿä¼šè‡ªåŠ¨æ ¹æ�®å½“å‰�的负载情况ã€�任务优先级安æ�’一个空闲的线程æ�¥æ‰§è¡Œå®ƒã€‚使用 TaskPool çš„æ ¸å¿ƒåœ¨äº�Concurrent装饰器。我们需è¦�将耗时的逻辑å°�装æˆ�ä¸€ä¸ªç‹¬ç«‹çš„å‡½æ•°å¹¶æ‰“ä¸Šè¿™ä¸ªæ ‡è®°ã€‚è¿™ä¸ªå‡½æ•°å¿…é¡»æ˜¯çº¯å‡½æ•°æˆ–è€…é�™æ€�方法ä¸�能ä¾�赖外部的 UI 组件状æ€�å› ä¸ºå®ƒæ˜¯è¦�被å�‘é€�到å�¦ä¸€ä¸ªçº¿ç¨‹å�»æ‰§è¡Œçš„。当我们调用taskpool.execute时系统会将函数的å�‚æ•°åº�列化å��æ‹·è´�到工作线程执行完毕å��å†�将结æ�œåº�列化拷è´�å›�主线程。这ç§�机制é��常适å�ˆé‚£äº›ç‹¬ç«‹ã€�çŸæ—¶ã€�高 CPU 消耗的任务比如大图å�‹ç¼©ã€�å¤�æ�‚算法计算ç‰ã€‚TaskPool 会自动进行负载å�‡è¡¡å½“任务过多时会自动扩容空闲时会自动缩容完全ä¸�需è¦�å¼€å�‘者æ“�心。三ã€� Worker常驻å��å�°çš„专è�Œç®¡å®¶æ—¢ç„¶æœ‰äº† TaskPool为什么还需è¦�WorkerTaskPool 虽好但它本质上是任务导å�‘的执行完就释放。如æ�œä½ 的应用需è¦�一个长时间è¿�行的å��å�°çº¿ç¨‹æ¯”如需è¦�一直ä¿�æŒ�一个 WebSocket é•¿è¿�æ�¥æˆ–者需è¦�一个常驻的数æ�®åº“读写å�¥æŸ„那么 TaskPool å°±ä¸�太å�ˆé€‚了。Worker æ›´åƒ�æ˜¯ä¸€ä¸ªä½ ä¸“é—¨é›‡ä½£çš„â€œå…¨è�Œå‘˜å·¥â€�ã€‚ä½ éœ€è¦�手动创建它new worker.ThreadWorker它拥有独立的文件上下文worker.tså¹¶ä¸”ä¼šä¸€ç›´å˜æ´»ç›´åˆ°ä½ 显å¼�地调用terminate销æ¯�它。Worker 适å�ˆå¤„ç�†é‚£äº›ç”Ÿå‘½å‘¨æœŸè¾ƒé•¿ã€�状æ€�需è¦�ä¿�æŒ�的场景。在 Worker çº¿ç¨‹ä¸æˆ‘们通过postMessageå�‘主线程å�‘é€�消æ�¯ä¸»çº¿ç¨‹é€šè¿‡onmessageæ�¥æ”¶ã€‚虽然æµ�程比 TaskPool ç¹�ç��一些但它æ��供了更精细的线程æ�§åˆ¶èƒ½åŠ›ã€‚éœ€è¦�注æ„�的是Worker 的数é‡�是有é™�制的通常最多 8 个且创建和销æ¯�都有一定的资æº�开销所以å�ƒä¸‡ä¸�è¦�滥用。四ã€� 通信机制ä»�“拷è´�â€�到“转移â€�å‰�é�¢æ��到Actor 模å�‹çš„æ•°æ�®é€šä¿¡ä¾�èµ–äº�åº�列化和å��åº�列化也就是拷è´�Structured Clone。对äº�普通的 JSON 对象或å°�æ•°æ�®è¿™ç§�å¼€é”€å‡ ä¹�å�¯ä»¥å¿½ç•¥ä¸�计。但如æ�œä½ è¦�ä¼ é€’ä¸€å¼ 10MB çš„ä½�图数æ�®æˆ–者一个巨大的 Float32Arrayæ‹·è´�带æ�¥çš„ CPU å’Œå†…å˜æ¶ˆè€—就是巨大的甚至å�¯èƒ½æŠµæ¶ˆå¤šçº¿ç¨‹å¸¦æ�¥çš„æ€§èƒ½çº¢åˆ©ã€‚为了解决这个问题鸿蒙引入了Transferable Object转移对象的概念。对äº� ArrayBuffer 这类二进制数æ�®æˆ‘们å�¯ä»¥é€‰æ‹©â€œè½¬ç§»â€�æ�§åˆ¶æ�ƒè€Œä¸�是拷è´�。就åƒ�我把手里的公文包直æ�¥é€’ç»™ä½ æˆ‘è¿™é‡Œæ²¡æœ‰äº†ä½ é‚£é‡Œæœ‰äº†ä¸é—´ä¸�需è¦�å¤�å�°æ–‡ä»¶ã€‚在代ç �䏿ˆ‘们在postMessage或者 TaskPool çš„å�‚æ•°ä¸å�¯ä»¥å°†è¿™äº›å¯¹è±¡æ ‡è®°ä¸º Transferable。一旦转移å�Ÿçº¿ç¨‹å°±æ— 法å†�访问这å�—内å˜äº†è®¿é—®ä¼šæŠ¥é”™ä»�而å®�ç�°äº†é›¶æ‹·è´�Zero Copyçš„æ��速通信。这是在处ç�†éŸ³è§†é¢‘æµ�ã€�图åƒ�处ç�†ç‰å¤§æ•°æ�®åœºæ™¯ä¸‹çš„å¿…æ�€æŠ€ã€‚五ã€�å®�战示例下é�¢æ‹Ÿäº†ä¸€ä¸ªâ€œå›¾ç‰‡é«˜æ–¯æ¨¡ç³Šå¤„ç�†â€�的耗时场景。我们在主界é�¢ä¸Šæ”¾äº†ä¸€ä¸ªåŠ è½½åœˆé€šè¿‡ TaskPool 在å��å�°çº¿ç¨‹è¿›è¡Œæ•°äº¿æ¬¡çš„æ•°å¦è¿�ç®—ä½ ä¼šå�‘ç�°ä¸»ç•Œé�¢çš„åŠ è½½åœˆä¾�然转得ä¸�滑æµ�畅完全没有被å�¡é¡¿ã€‚import { taskpool } from kit.ArkTS; import { promptAction } from kit.ArkUI; // ------------------------------------------------------------- //
定义并å�‘任务函数 // ------------------------------------------------------------- // 必须使用 Concurrent 装饰器 // 这个函数将在独立的线程ä¸è¿�行ä¸�能访问外部的 this 或 UI 状æ€� Concurrent function heavyImageProcess(buffer: ArrayBuffer, iterations: number): ArrayBuffer { // 模拟耗时æ“�作例如对图片åƒ�ç´ è¿›è¡Œå¤�æ�‚的矩阵è¿�ç®— const startTime Date.now(); console.info([TaskPool] 任务开始执行); // 这里我们用空循ç�¯æ¨¡æ‹Ÿ CPU 密集å�‹è®¡ç®— // å®�际场景ä¸è¿™é‡Œæ˜¯å¯¹ buffer 进行åƒ�ç´ çº§æ“�作 let result 0; for (let i 0; i iterations; i) { result Math.sqrt(i) * Math.sin(i); } const endTime Date.now(); console.info([TaskPool] 任务完æˆ�耗时: ${endTime - startTime}msè®¡ç®—æ ¡éªŒå€¼: ${result.toFixed(
}); // è¿”å›�处ç�†å��的数æ�® (æ¤å¤„ç›´æ�¥è¿”å›�å�Ÿæ•°æ�®ç”¨äº�演示) // 注æ„�默认情况下返å›�值会通过åº�列化拷è´�å›�主线程 // 如æ�œä½¿ç”¨ setTransferList则ä¸�需è¦�æ‹·è´� return buffer; } Entry Component struct ThreadConcurrencyPage { State isProcessing: boolean false; State processResult: string ç‰å¾…处ç�†...; State progressValue: number 0; // 用äº�模拟 UI 动画的定时器验è¯�主线程是å�¦å�¡æ» private animationTimer: number -1; aboutToAppear(): void { // å�¯åŠ¨ä¸€ä¸ªä¸»çº¿ç¨‹åŠ¨ç”»è¯�æ˜� UI 没å�¡æ» // æ¯� 50ms 更新一次进度让进度ç�¯è½¬åЍ this.animationTimer setInterval(() { this.progressValue (this.progressValue
% 100; },
; } aboutToDisappear(): void { clearInterval(this.animationTimer); } // ------------------------------------------------------------- //
触å�‘ TaskPool 任务 // ------------------------------------------------------------- async startAsyncTask() { if (this.isProcessing) return; this.isProcessing true; this.processResult æ£åœ¨å��å�°çº¿ç¨‹å…¨é€Ÿè®¡ç®—ä¸...; try { // 模拟一个 10MB 的图片数æ�® const imageSize 1024 * 1024 * 10; const mockBuffer new ArrayBuffer(imageSize); // 创建 Task 对象 // å�‚æ•°1: Concurrent 函数 // å�‚æ•°
..n: ä¼ é€’ç»™å‡½æ•°çš„å�‚æ•° const task new taskpool.Task(heavyImageProcess, mockBuffer,
; // ã€�性能优化关键点】 // 如æ�œæ•°æ�®å¾ˆå¤§å¼ºçƒˆå»ºè®®ä½¿ç”¨ setTransferList å°† ArrayBuffer çš„æ�§åˆ¶æ�ƒâ€œè½¬ç§»â€�ç»™å�线程 // è¿™æ ·ä¸»çº¿ç¨‹çš„ mockBuffer å°†ç�¬é—´å�˜å¾—ä¸�å�¯ç”¨ (byteLength 为
但é�¿å…�了巨大的拷è´�开销 // 如æ�œå¼€å�¯ä¸‹é�¢è¿™è¡Œæ³¨é‡Šä¼ å…¥å�线程是零拷è´�但主线程这边的 mockBuffer 就废了 // task.setTransferList([mockBuffer]); // 执行任务并ç‰å¾…结æ�œ // execute è¿”å›�的是 Promiseä¸�会阻å¡�当å‰�主线程 const result await taskpool.execute(task); // ç±»å�‹æ–言确ä¿�è¿”å›�的是 ArrayBuffer const resultBuffer result as ArrayBuffer; this.processResult 处ç�†æˆ�功\næ•°æ�®å¤§å°�: ${(resultBuffer.byteLength / 1024 /
.toFixed(
} MB; promptAction.showToast({ message: ��任务执行完毕 }); } catch (e) { console.error(Task execution failed: ${JSON.stringify(e)}); this.processResult 处�失败; } finally { this.isProcessing false; } } build() { Column() { Text(TaskPool 多线程并�) .fontSize(
.fontWeight(FontWeight.Bold) .margin({ top: 40, bottom: 20 }) // 状æ€�展示区 Column({ space: 20 }) { // 一个一直在动的进度æ�¡ç”¨äº�检测 UI 线程是å�¦å�¡é¡¿ // 如æ�œä¸»çº¿ç¨‹è¢«é˜»å¡�这个进度æ�¡ä¼šå�œæ¢è½¬åЍ Progress({ value: this.progressValue, total: 100, type: ProgressType.Ring }) .width(
.height(
.color(#0A59F
.style({ strokeWidth: 10 }) .animation({ duration: 100 }) Text(this.processResult) .fontSize(
.fontColor(#
.textAlign(TextAlign.Center) .padding(
} .width(90%) .padding(
.backgroundColor(Color.White) .borderRadius(
.shadow({ radius: 10, color: #1A000000 }) .margin({ bottom: 40 }) // æ“�作按钮 Button(this.isProcessing ? æ£åœ¨è®¡ç®—ä¸... : 开始耗时计算 (5000万次)) .width(80%) .height(
.backgroundColor(this.isProcessing ? #CCCCCC : #0A59F
.enabled(!this.isProcessing) .onClick(() { this.startAsyncTask(); }) Text(��说�\n点击按钮�TaskPool 会在��线程执行数�万次浮点�算。请观察上方的进度圈它�然���畅转动说�主线程UI线程未被阻�。) .fontSize(
.fontColor(#
.padding(
.lineHeight(
} .width(100%) .height(100%) .backgroundColor(#F1F3F
} }å…ã€� 总结ä¸�å®�战在鸿蒙 HarmonyOS 6 的开å�‘ä¸â€œä¸»çº¿ç¨‹å�ªå�š UI 渲染和轻é‡�逻辑耗时任务一律扔给å��å�°â€�应当æˆ�为我们的肌肉记忆。对äº�ç»�大多数场景TaskPool是最简å�•高效的选择它å±�蔽了线程管ç�†çš„å¤�æ�‚性而对äº�需è¦�é•¿æ—¶ä¿�活的逻辑Worker则是ä¸�å�¯æˆ–缺的补充。å�Œæ—¶æˆ‘们è¦�善用ArrayBuffer和转移机制æ�¥ä¼˜åŒ–大数æ�®é€šä¿¡çš„æ€§èƒ½ã€‚
十九岁中国免费高清电视-十九岁中国免费高清电视应用