1# 并发常见问题 2<!--Kit: ArkTS--> 3<!--Subsystem: CommonLibrary--> 4<!--Owner: @lijiamin2025--> 5<!--Designer: @weng-changcheng--> 6<!--Tester: @kirl75; @zsw_zhushiwei--> 7<!--Adviser: @ge-yafang--> 8 9## TaskPool任务不执行快速定位指导 10 11开发者发现TaskPool任务不执行时,可按照以下步骤快速定位。 12 131. **taskpool.execute接口是否调用**。 14 15 taskpool.execute被调用时,Hilog会打印TaskPool调用态日志(Task Allocation: taskId:)。 16 如果发现没有该维测日志表明taskpool.execute实际未调用,应用需排查taskpool.execute之前的其他业务逻辑是否执行完成。 17 18 ```ts 19 import { taskpool } from '@kit.ArkTS'; 20 21 @Concurrent 22 function createTask(a: number, b:number): number { 23 let sum = a + b; 24 return sum; 25 } 26 27 @Entry 28 @Component 29 struct Index { 30 @State message: string = 'Hello World'; 31 32 build() { 33 Row() { 34 Column() { 35 Text(this.message) 36 .fontSize(50) 37 .fontWeight(FontWeight.Bold) 38 .onClick(() => { 39 console.info("test start"); 40 // 其他业务逻辑 41 // ... 42 let task: taskpool.Task = new taskpool.Task(createTask, 1, 2); 43 taskpool.execute(task); 44 // ... 45 }) 46 } 47 .width('100%') 48 } 49 .height('100%') 50 } 51 } 52 53 // 如果test start在控制台打印,但是并未出现Task Allocation: taskId:的日志,则taskpool.execute没有执行,应用需要排查其他业务逻辑。 54 ``` 55 562. **TaskPool任务是否被执行**。 57 58 调用taskpool.execute接口会打印TaskPool**调用态日志**(Task Allocation: taskId:)。 59 定位到目标任务对应的Task Allocation: taskId:日志后,在日志中搜索taskId后跟随的Id号,正常情况会打印**执行态日志**(Task Perform: name:)和**结束态日志**(Task PerformTask End: taskId:)。 60 61 1. 如果只有调用态日志,没有执行态日志。可能是由于先执行的TaskPool任务阻塞了TaskPool工作线程,导致TaskPool工作线程不可用,后执行的TaskPool任务无法执行。应用可以排查自身业务逻辑,或者通过trace进一步定位。 62 63 2. 如果只有调用态日志和执行态日志,没有结束态日志。应用优先分析自定义的TaskPool任务内的业务逻辑是否存在阻塞操作。 64 65 3. 如果调用态日志和执行态日志时间间隔较久,且应用关注任务的执行时机,可以按照以下步骤继续分析。 66 67 1. 查看是否发生大量TaskPool任务堆积未执行的情况。如果在较短时间内执行大量任务(出现大量调用态日志),后执行的任务需要等待前置任务执行完。此时可以检查TaskPool的扩容情况,如果在调用态日志打印之前,TaskPool工作线程数量已扩容到接近上限(上限数量为日志片段log2中的maxThreads字段),则可能是短时间内任务数量太多导致,应用可以通过合理设置优先级将重要任务和有时效要求的任务优先执行。 68 69 2. 查看前置执行的TaskPool任务是否本身耗时较长或者发生阻塞。如果前置任务本身耗时较长,应用可以通过合理设置优先级解决。如果前置任务发生了意料之外的阻塞(一段时间后阻塞解除),应用需要排查自身业务逻辑。 70 71 ```ts 72 // hilog 日志片段(模拟),格式如下,具体数值由应用运行时决定 73 // log1: 大量任务提交 74 taskpool:: Task Allocation: taskId: , priority: , executeState: 75 taskpool:: Task Allocation: taskId: , priority: , executeState: 76 taskpool:: Task Allocation: taskId: , priority: , executeState: 77 taskpool:: Task Allocation: taskId: , priority: , executeState: 78 taskpool:: Task Allocation: taskId: , priority: , executeState: 79 ... 80 // log2: 扩容日志 81 taskpool:: maxThreads: , created num: , total num: 82 // log3: 执行态日志 83 taskpool:: Task Perform: name: , taskId: , priority: 84 ``` 85 863. **TaskPool任务执行时是否发生异常**。 87 88 1. 如果在执行TaskPool任务过程中发生JS异常,TaskPool会捕获该JS异常并通过taskpool.execute().catch((e:Error)=>{})将异常信息返回,应用需要查看异常信息并修复。 89 90 ```ts 91 import { taskpool } from '@kit.ArkTS'; 92 93 @Concurrent 94 function createTask(a: number, b:number) { 95 let sum = a + b; 96 return sum; 97 } 98 99 @Entry 100 @Component 101 struct Index { 102 @State message: string = 'Hello World'; 103 104 build() { 105 Row() { 106 Column() { 107 Text(this.message) 108 .fontSize(50) 109 .fontWeight(FontWeight.Bold) 110 .onClick(() => { 111 console.info("test start"); 112 // 其他业务逻辑 113 // ... 114 let task: taskpool.Task = new taskpool.Task(createTask, 1, 2); 115 taskpool.execute(task).then((res: object)=>{ 116 // 任务执行完处理结果 117 // ... 118 }).catch((e: Error)=>{ 119 // 任务发生异常后处理异常 120 // ... 121 }) 122 // ... 123 }) 124 } 125 .width('100%') 126 } 127 .height('100%') 128 } 129 } 130 ``` 131 132 2. 如果.catch分支无异常信息返回,但是应用通过TaskPool任务实现的功能发生问题,应用需要查看TaskPool任务逻辑是否发生阻塞,导致功能异常。 133 134## TaskPool任务执行慢排查思路 135 136开发者发现TaskPool任务的调用态日志(如taskpool::add taskId:或者taskpool::Task Allocation: taskId:)与执行态日志(taskpool::Task Perform: name:)间隔时间较长时,可参考该排查指导进行问题定位。 137 138### 排查方向:出问题的TaskPool任务优先级较低,应用后续新增较多优先级更高的TaskPool任务,导致原有低优先级的TaskPool任务执行延后 139 140**场景示例一** 141 142某应用创建了低优先级的TaskPool任务,且其他业务场景依赖这个任务执行。后续,应用又创建了很多中优先级任务,导致原有的低优先级任务执行时机延后,其他业务场景未按计划时间点完成任务。 143 144**解决方案** 145 146对完成执行时间点有要求的任务以低优先级执行不是好的选择,应用需要根据业务场景设置合理的任务优先级,且合理搭配任务优先级。 147 148**场景示例二** 149 150应用对某个TaskPool任务(简称taskA)执行时间有要求,执行超过5s时有检测机制。应用将taskA设置为MEDIUM优先级,在taskA前以MEDIUM优先级执行了较多其他任务,且这些任务耗时3s/5s不等,将已有线程和新扩容的线程均占满,导致taskA从taskpool.execute到执行结束超过5s。 151 152**解决方案** 153 1541.分析其他任务执行耗时3s/5s是否合理;2.调整taskA优先级。 155 156### 排查方向:晚执行的TaskPool任务是串行任务或者依赖其他任务 157 158**场景示例** 159 160如果问题场景对应的TaskPool任务是串行队列任务,查看该串行队列内前面任务的执行情况。如日志片段1所示该串行队列有四个任务,问题场景对应的是第四个任务,查看日志片段2发现第二个任务执行了2s,对于应用业务逻辑是不正常的。 161```ts 162// hilog 日志片段1(模拟) 163// seqRunner共有四个任务 164taskpool:: taskId 389508780288 in seqRunner 393913878464 immediately. 165taskpool:: add taskId: 394062838784 to seqRunner 393913878464 166taskpool:: add taskId: 393918679936 to seqRunner 393913878464 167taskpool:: add taskId: 393918673408 to seqRunner 393913878464 168 169// hilog 日志片段2(模拟) 170// 查看第二个任务, 发现任务执行到执行结束间隔2s 17118:28:28.223 taskpool:: taskId 394062838784 in seqRunner 393913878464 immediately. 17218:28:28.224 taskpool:: Task Perform: name : , taskId : 394062838784, priority : 0 17318:28:30.243 taskpool:: Task Perform End: taskId : 394062838784, performResult : Successful 174``` 175 176**解决方案** 177 178应用继续分析第二个任务中的业务逻辑是否存在耗时操作。 179 180### 排查方向:@Concurrent标记的方法所在的ets文件里import过多模块 181 182**场景示例** 183 184TaskPool第一次执行任务慢,间隔几百毫秒,原因是子线程反序列化之前,会将@Concurrent标记的方法所在的ets文件import的所有模块都初始化,导致出现任务调度慢的情况。应用可通过trace进一步定位,如果反序列化成功前有许多init module的trace,应用自行排查ets文件是否import过多模块。 185 186**解决方案** 187 1881.可拆分@Concurrent方法到单独的ets文件,减少模块初始化时间;2.使用延迟加载([lazy import](arkts-lazy-import.md))。 189 190## TaskPool序列化失败问题定位指导 191 192**问题现象** 193 1941. JS异常 195 196 入参序列化失败 197 198 ```ts 199 // API version 20之前版本 200 Error message:An exception occurred during serialization, taskpool: failed to serialize arguments. 201 202 // API version 20及之后版本 203 Error message:An exception occurred during serialization, taskpool: failed to serialize arguments. 204 Serialize error: Serialize don't support object type: 205 ``` 206 207 返回结果序列化失败 208 209 ```ts 210 // API version 20之前版本 211 Error message:An exception occurred during serialization, taskpool: failed to serialize result. 212 213 // API version 20及之后版本 214 Error message:An exception occurred during serialization, taskpool: failed to serialize result. 215 Serialize error: Serialize don't support object type: 216 ``` 217 2182. Hilog错误日志 219 220 ```ts 221 // API version 20之前版本 222 [ecmascript] Unsupport serialize object type: 223 [ecmascript] ValueSerialize: serialize data is incomplete 224 225 // API version 20及之后版本 226 [ecmascript] Serialize don't support object type: 227 [ecmascript] ValueSerialize: serialize data is incomplete 228 ``` 229 230**问题原因** 231 232TaskPool实现任务的函数(Concurrent函数)入参和返回结果需满足线程间通信支持的对象类型,详情请查看[线程间通信对象](../reference/apis-arkts/js-apis-taskpool.md#序列化支持类型)。当Concurrent函数的入参或返回结果是线程间通信不支持的对象类型时,会出现上述现象。应用可以结合Hilog日志中打印的对象类型进一步排查通信对象是否符合要求。 233 234**场景示例** 235 2361. 应用在启动TaskPool任务时,在Concurrent函数中传入线程间通信不支持的对象类型,导致抛出入参序列化失败异常。 237**解决方案**:应用需要查看[线程间通信对象](../reference/apis-arkts/js-apis-taskpool.md#序列化支持类型)排查Concurrent函数入参。 238 2392. 应用在启动TaskPool任务时,抛出入参序列化失败异常,同时Hilog打印错误日志Unsupport serialize object type: Proxy(API version 20及之后版本打印错误日志:Serialize error: Serialize don't support object type: Proxy)。基于错误日志可知应用在Concurrent函数中传入代理对象,排查代码发现入参使用了@State装饰器,导致原对象实际上变为Proxy代理对象,代理对象不属于线程间通信支持的对象类型。 240**解决方案**:TaskPool不支持@State、@Prop等装饰器修饰的复杂类型,具体内容可见[TaskPool注意事项](taskpool-introduction.md#taskpool注意事项)。应用需要去掉@State装饰器。 241 2423. 应用执行TaskPool任务时,抛出返回结果序列化失败异常,排查代码发现Concurrent函数返回结果是不支持的序列化类型。 243 244 ```ts 245 // utils.ets 246 @Concurrent 247 export function printArgs(args: number) { 248 return args; 249 } 250 251 // index.ets 252 import { taskpool } from '@kit.ArkTS' 253 import { BusinessError } from '@kit.BasicServicesKit' 254 import { printArgs} from './utils' 255 @Concurrent 256 function createTask(a: number, b:number) { 257 let sum = a + b; 258 // task1: 不支持的序列化类型 259 let task1: taskpool.Task = new taskpool.Task(printArgs, sum); 260 return task1; 261 } 262 263 function executeTask() { 264 // task 265 let task: taskpool.Task = new taskpool.Task(createTask, 1, 2); 266 taskpool.execute(task).then((res) => { 267 }).catch((e: BusinessError) => { 268 // 打印“返回结果序列化失败”异常信息 269 console.error("execute task failed " + e.message); 270 }) 271 } 272 ``` 273 274 **解决方案**:task1在.then中创建执行,Concurrent函数的返回结果设置为可序列化的类型。 275 276 ```ts 277 // utils.ets 278 @Concurrent 279 export function printArgs(args: number) { 280 return args; 281 } 282 283 // index.ets 284 import { taskpool } from '@kit.ArkTS' 285 import { BusinessError } from '@kit.BasicServicesKit' 286 import { printArgs} from './utils' 287 @Concurrent 288 function createTask(a: number, b:number) { 289 // 支持的序列化类型 290 let sum = a + b; 291 return sum; 292 } 293 294 function executeTask() { 295 // task 296 let task: taskpool.Task = new taskpool.Task(createTask, 1, 2); 297 taskpool.execute(task).then((res) => { 298 // task1 299 let task1: taskpool.Task = new taskpool.Task(printArgs, res); 300 }).catch((e: BusinessError) => { 301 console.error("execute task failed " + e.message); 302 }) 303 } 304 ``` 305 306## Sendable类A的实例对象a传递到子线程后,使用a instanceof A判断返回false 307 308应用在子线程使用instanceof接口时,需要在导出Sendable类A的ets文件使用"use shared"指令标记该模块为[共享模块](../arkts-utils/arkts-sendable-module.md)。 309 310**代码示例** 311 312```ts 313// pages/index.ets 314import { worker, ErrorEvent } from '@kit.ArkTS' 315import { A } from './sendable' 316const workerInstance = new worker.ThreadWorker('../workers/Worker.ets'); 317function testInstanceof() { 318 let a = new A(); 319 if (a instanceof A) { 320 // 打印test instanceof in main thread success 321 console.info("test instanceof in main thread success"); 322 } else { 323 console.info("test instanceof in main thread failed"); 324 } 325 workerInstance.postMessageWithSharedSendable(a); 326 workerInstance.onerror = (err: ErrorEvent) => { 327 console.error("worker err :" + err.message) 328 } 329} 330 331testInstanceof() 332``` 333```ts 334// pages/sendable.ets 335"use shared" 336@Sendable 337export class A { 338 name: string = "name"; 339 printName(): string { 340 return this.name; 341 } 342} 343``` 344```ts 345// workers/Worker.ets 346import { A } from '../pages/sendable' 347import { worker, ThreadWorkerGlobalScope, MessageEvents } from '@kit.ArkTS' 348 349const workerPort: ThreadWorkerGlobalScope = worker.workerPort; 350workerPort.onmessage = (e: MessageEvents) => { 351 let a : A = e.data as A; 352 if (a instanceof A) { 353 // 打印test instanceof in worker thread success 354 console.info("test instanceof in worker thread success"); 355 } else { 356 console.info("test instanceof in worker thread failed"); 357 } 358} 359``` 360 361## 使用Sendable特性抛JS异常排查指导 362 363由于Sendable特性存在固定布局、Sendable无法持有非Sendable等规格限制,开发者在进行Sendable改造时可能触发相关约束,导致抛出相应的JS异常。应用可参考以下内容进行代码排查。 364 365### 属性类型不一致异常 366 367**问题现象** 368 369```ts 370JS异常:TypeError: Cannot set sendable property with mismatched type 371``` 372 373**问题原因与解决方案** 374 375由于ArkTS运行时在属性赋值时会严格进行类型一致性校验,如果定义的属性类型与传入的对象类型不一致,会抛出上述JS异常。应用需要基于JS异常栈信息,定位排查相应的业务逻辑。 376 377**场景示例** 378 3791. 应用在向子线程传递Sendable类A的实例对象时,抛出类型不一致异常。基于JS栈定位到问题发生在创建类A的实例对象时,排查后发现应用当前模块与其他模块联调时,其他模块未使用Sendable类B封装数据集。 380**解决方案** : 应用当前模块将其他模块传递的数据使用Sendable类重新封装。 381 382 ```ts 383 @Sendable 384 export class B { 385 constructor() {} 386 } 387 388 @Sendable 389 export class A { 390 constructor(b: B) { 391 this.b = b; 392 } 393 public b: B | undefined = undefined; 394 } 395 ``` 396 3972. 应用查看JS异常栈发现运行this.g = g赋值语句时,抛出类型不一致异常。排查代码后发现属性g使用了@State装饰器,导致原对象变为Proxy代理对象,造成定义类型与传入类型不一致。 398**解决方案**:去掉@State装饰器 399 400### 新增属性异常 401 402**问题现象** 403 404```ts 405JS异常:TypeError: Cannot add property in prevent extensions 406``` 407 408**问题原因与解决方案** 409 410由于Sendable类的布局固定,不允许增删属性,对Sendable对象新增属性时会抛出上述JS异常。应用需要基于JS异常栈定位到对应的ts文件代码行,排查相应的业务逻辑。 411 412**异常场景示例** 413 4141. 应用基于业务需要在同一个ets文件定义了同名的Sendable类和namespace,抛出新增属性异常。由于ts会合并同名的class和namespace,将namespace中的导出的内容附加到同名类上,实际上是对Sendable类新增属性,导致抛出上述异常。 415**解决方案**:规格限制,暂不支持合并同名Sendable class和namespace。 416 4172. 应用在HAR中使用Sendable特性时,抛出新增属性异常。查看JS异常栈,发现异常代码行定位在js文件,而Sendable特性不支持在js文件中使用,导致抛出非预期的异常。 418**解决方案**:在HAR中使用Sendable特性时,[配置tsHAR](../quick-start/har-package.md#编译生成ts文件)。 419 4203. 应用在Local Test单元测试或预览器中使用Sendable特性时,抛出新增属性异常。由于Sendable特性暂不支持在Local Test和预览器中使用,导致抛出非预期的异常。 421**解决方案**:规格限制,暂不支持。 422 423## ArkTS提供的Promise能力的原理是什么 424 425Promise是ArkTS提供的异步并发能力,是标准的JS语法。详情请查看[Promise](async-concurrency-overview.md#promise)概述。 426 427## TaskPool线程是否可以执行不需要@Concurrent和@Sendable修饰的JS闭包函数 428 429TaskPool执行的任务函数必须使用@Concurrent装饰器修饰,由于Concurrent函数不能访问闭包,因此函数内不可调用当前文件的其他普通函数,详情请参考[TaskPool注意事项](taskpool-introduction.md#taskpool注意事项)。但是,开发者可以通过给Concurrent函数传参的方式,传入@Sendable装饰器修饰的普通function和Async function,在Concurrent函数内调用Sendable function。 430 431因此,TaskPool线程目前不支持执行普通的JS闭包函数。如果有相关诉求,开发者可以根据业务需要使用[Worker](worker-introduction.md)并发能力进行业务改造。 432 433## TaskPool任务执行后的结果如何保存到自定义的数据结构 434 435**问题描述** 436 437TaskPool的任务执行函数Concurrent函数只能使用局部变量和函数入参,TaskPool任务执行后的结果应该如何保存到自定义的数据结构。 438 439**解决方案** 440 4411. 自定义Sendable类。[Sendable对象](arkts-sendable.md)可以在不同的子线程中共享,开发者可以将任务执行后的结果保存到Sendable对象上。 442 4432. TaskPool任务执行后的结果可以在.then中返回,需要保存的数据如果仅在当前线程使用,可以在.then中将执行结果保存到自定义的数据结构中。 444 445 ```ts 446 // sendable.ets,与Index.ets在同级目录下 447 @Sendable 448 export class testClass { 449 name: string = "test"; 450 setName(name: string) { 451 this.name = name; 452 } 453 getName(): string { 454 return this.name; 455 } 456 } 457 ``` 458 459 ```ts 460 // Index.ets 461 import { taskpool } from '@kit.ArkTS' 462 import { BusinessError } from '@kit.BasicServicesKit' 463 import { testClass } from './sendable' 464 465 @Concurrent 466 function createTask(a: number): string { 467 return `test${a}`; 468 } 469 function executeTask() { 470 let testObject: testClass = new testClass(); 471 let task: taskpool.Task = new taskpool.Task(createTask, 1) 472 taskpool.execute(task).then((res) => { 473 testObject.setName(res as string); 474 console.info('execute task success, name is ' + testObject.getName()); 475 }).catch((e: BusinessError) => { 476 console.error('execute task error: ' + e.message); 477 }) 478 } 479 ``` 480 481## Sendable类在子线程无法加载 482 483**问题描述** 484 485Sendable装饰器修饰的类与Observed装饰器修饰的类定义在同一个ets文件中,在TaskPool子线程加载Sendable类时捕获到错误信息:SendableItem is not initialized。 486 487```ts 488// Index.ets: 在Index页面新增以下代码 489import { taskpool } from '@kit.ArkTS' 490import { BusinessError } from '@kit.BasicServicesKit' 491import { SendableItem } from './sendable' 492 493@Concurrent 494function createTask() { 495 let data = new SendableItem(); 496} 497 498function executeTask() { 499 let task = new taskpool.Task(createTask); 500 taskpool.execute(task).then((res) => { 501 console.info('execute task success'); 502 }).catch((e: BusinessError) => { 503 console.error('execute task error: ' + e.message); 504 }) 505} 506 507executeTask(); 508``` 509 510```ts 511// sendable.ets 512@Observed 513export class NormalItem { 514 age: number = 0; 515} 516 517@Sendable 518export class SendableItem { 519 name: string = ''; 520} 521``` 522 523**根因分析** 524 525Observed装饰器仅支持在UI线程使用,不能在子线程、Worker、TaskPool中直接或者间接使用,否则会导致应用功能失效甚至crash。由于sendable.ets文件中定义了Observed装饰器修饰的类,即使该类没有被显式调用也可能被解析执行,当解析到Observed这类UI装饰器时则抛出异常:Observed is not defined,导致当前文件中的其他模块的解析被中断。在TaskPool子线程加载Sendable类时抛出异常:SendableItem is not initialized。 526 527**解决方案** 528 529将Observed装饰器修饰的类NormalItem剥离到单独的ets文件后,TaskPool子线程再去加载Sendable类SendableItem,应用运行符合预期。 530 531```ts 532// Index.ets: 在Index页面新增以下代码 533import { taskpool } from '@kit.ArkTS' 534import { BusinessError } from '@kit.BasicServicesKit' 535import { SendableItem } from './sendable' 536 537@Concurrent 538function createTask() { 539 let data = new SendableItem(); 540} 541 542function executeTask() { 543 let task = new taskpool.Task(createTask); 544 taskpool.execute(task).then((res) => { 545 console.info('execute task success'); 546 }).catch((e: BusinessError) => { 547 console.error('execute task error: ' + e.message); 548 }) 549} 550 551executeTask(); 552``` 553 554```ts 555// sendable.ets 556@Sendable 557export class SendableItem { 558 name: string = ''; 559} 560``` 561 562```ts 563// ui.ets 564@Observed 565export class NormalItem { 566 age: number = 0; 567} 568``` 569