鸿蒙「TaskPool|Worker」多线程并发利用详解,这一篇充足! ...

打印 上一主题 下一主题

主题 874|帖子 874|积分 2626

异步方法和并发线程

我们知道在TS这类语言基于JS,在ES7拥有的语法关键字。为实现异步操作。那鸿蒙ArkTs中也可利用async声明并利用异步操作,它和鸿蒙的并发线程操作有什么区别?
   只管异步方法和并发线程都可以用于处理耗时操作,但它们的目标和用法有所不同。
但在某些层面上,利用效果是一致的。如声明周期方法中为防止主线程阻塞影响响应性能,可利用async异步方法操作实现。
  异步方法更侧重于代码的编写方式,通过async和await语法来处理异步操作,而不需要手动创建和管理线程。
并发线程更侧重于线程的管理和控制,你可以更灵活地控制线程的创建、执行和烧毁,但需要手动处理线程的生命周期和同步题目。
概念介绍

鸿蒙的多线程并发TaskPool和Worker,他们具有相同内存模子,线程隔断离内存不共享。在项目中若利用到,有几个较告急的条件或特点这里简单作出列举。
   CPU麋集型使命,说白了是计算型耗时使命;
I/O麋集型使命,说白了是读写型耗时使命;
官方文档重点介绍了这两种基于多线程并发机制处理使命范例,我们是要深度思考下的?!「很有意义」,是否已经包含且指明了我们利用「TaskPool/Worker」来解决项目题目标方案呢?
  

  • Worker

    • 利用Worker,创建线程个数最多是64个。凌驾则创建失败。
    • 利用Worker,传输序列化数据大小限定在16MB。
    • 引用HAR/HSP前,首先要设置对HAR/HSP的依赖。不支持 跨HAP利用Worker线程文件。
    • 利用Worker模块时,需要在主线程中注册onerror接口,否则当worker线程出现异常时会发生jscrash题目。
    • 使命执行时长上限,无限定。

  • TaskPool

    • TaskPool内部会动态调整线程个数,不支持设置数量。
    • TaskPool线程池的数量会根据硬件条件、使命负载等情况动态调整。
    • 使命执行时长上限,为3分钟(执行耗时不能凌驾3分钟)。
    • Promise不支持跨线程通报,不能作为concurrent function的返回值。

利用详解

对Worker以及TaskPool的利用详解,个人预备以一种特殊的角度来详述。从我个人初始打仗及学习研究的视角,针对 如何选用、如何创建、如何利用、注意事项和条件限定 多个方面,全面剖析
选用

依据限定条件,若思量到使命执行时间已凌驾3分钟,传输数据不大。且需创建的线程个数仅几个这样子,思量选用Worker;若思量到使命执行时间较短,且会有大量的线程需要创建、烧毁和复用,不想手动对线程数量的控制,可思量选用TaskPool。假如利用条件上,都未超出两种限定条件,那么请随意。
创建|利用

Worker创建

Worker在进行创建利用时,有手动和主动两种方式,主动的较简单。手动创建Worker线程目录及文件时,还需同步进行相干设置。 「注意事项」 Worker线程文件需要放在"{moduleName}/src/main/ets/"目录层级之下,否则不会被打包到应用中。
   主动操作: 在moduleName目录下恣意位置,点击鼠标右键 > New > Worker,即可主动天生Worker的模板文件及设置信息,无需再手动在build-profile.json5中进行相干设置。
    主动创建演示:假如创建Worker文件 entry/src/main/ets/workers/MyTestWorker1.ets ; 「workers是我自己建的文件目录」则同时deveco studio将在build-profile.json5文件中发现主动设置信息如下
  1. /// build-profile.json5
  2. {
  3.   "apiType": "stageMode",
  4.   "buildOption": {
  5.     "sourceOption": {
  6.       "workers": [ // 这里就是自动生成的worker配置;换言之,手动创建Worker的话,需要在这里配置下信息
  7.         './src/main/ets/workers/MyTestWorker1.ets'
  8.       ]
  9.     }
  10.   },
  11.   "targets": [
  12.     {
  13.       "name": "default",
  14.       "runtimeOS": "HarmonyOS"
  15.     }
  16.   ]
  17. }
复制代码
创建MyTestWorker1.ets文件,主动天生Worker文件模板如下
   手动创建Worker文件的话,文件内容如监听方法和错误捕捉方法的监听,需要仿照模板编写。同时要在build-profile.json5文件中设置下设置信息。
  1. import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
  2. const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
  3. /**
  4. * Defines the event handler to be called when the worker thread receives a message sent by the host thread.
  5. * [当工作线程接收到主线程发送的消息时,onmessage将被触发]
  6. * The event handler is executed in the worker thread.
  7. * [onmessage将在工作线程中被执行]
  8. * @param e message data
  9. */
  10. workerPort.onmessage = (e: MessageEvents) => {
  11. }
  12. /**
  13. * Defines the event handler to be called when the worker receives a message that cannot be deserialized.
  14. * [当工作线程接收到无法反序列化的消息时,onmessageerror将被触发]
  15. * The event handler is executed in the worker thread.
  16. *[onmessageerror将在工作线程中被执行]
  17. * @param e message data
  18. */
  19. workerPort.onmessageerror = (e: MessageEvents) => {
  20. }
  21. /**
  22. * Defines the event handler to be called when an exception occurs during worker execution.
  23. * [在工作线程执行期间发生异常时,将会调用onerror程序]
  24. * The event handler is executed in the worker thread.
  25. *[onerror将在工作线程中被执行]
  26. * @param e error message
  27. */
  28. workerPort.onerror = (e: ErrorEvent) => {
  29. }
复制代码
Worker利用

Worker多线程并发,在创建工作线程文件后。在主线程中发起对工作线程调用时,传入文件地址作为入参。若传入地址不对,则报错提示:
  1. Error message:The worker file path is invalid path, the file path is invaild, can't find the file.
  2. Error code:
  3. SourceCode:
  4.         const workerThread: worker.ThreadWorker = new worker.ThreadWorker('entry/src/main/ets/workers/MyTestWorker1.ets');
复制代码

「注意事项」 如何传入精确工作线程文件路径?
工作线程创建过程解释说明,


  • 先在entry/src/main/ets 目录下创建了workers文件目录,(「注意事项」 Worker线程文件需要放在"{moduleName}/src/main/ets/"目录层级之下,否则不会被打包到应用中。)
  • 然后在该workers文件目录下新建工作线程文件MyTestWorker1.ets,
  • 此时build-file.json5中主动设置信息是 "workers": ['./src/main/ets/workers/MyTestWorker1.ets' ]
  • 但不能直接利用这个设置的信息地址作为入参,而现实应该传入的入参地址,示例「可自行对比区别」: const workerThread: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/MyTestWorker1.ets');
   手动编写一个简单DEMO,在UI界面onPageShow生命周期方法中创建Worker实例对象,构造方法中传入Worker线程文件的路径。实现主线程和worker工作线程之间的通讯逻辑。
利用Worker实现多线程并发这一能力,首要要搞懂那里写执行耗时使命、在那里开启且如何开启线程 这就和安卓起一个Thread线程的思路很像。
  「利用要点」「注意事项」
   worker.ThreadWorker的实例对象是向Worker线程文件发送消息或接收Worker线程文件发送的消息。说白了就是开启线程执行的位置,如通过postMessage发送消息。即可关照worker线程文件中方法执行耗时使命。
worker.ThreadWorker就是用来开启线程工作用的。
  workerThread.onmessage方法,监听并接收Worker线程文件发出的消息。好比耗时线程工作完成了,需要告知主线程此时此刻的进度
workerThread.postMessage方法,向Worker线程文件发送消息。好比关照耗时线程开始工作
  1. /// 下面是部分主要代码,为方便阅读非重要内容已省略
  2. /// ets/pages/HomePage.ets
  3. import { worker, MessageEvents, ErrorEvent } from '@kit.ArkTS';
  4. @Entry
  5. @Component
  6. struct HomePage {
  7.   onPageShow(): void {
  8.     // constructor(scriptURL: string, options?: WorkerOptions);
  9.     // 构造方法入参Worker线程文件路径「关注」
  10.     const workerThread: worker.ThreadWorker = new worker.ThreadWorker('entry/src/main/ets/workers/MyTestWorker1.ets');
  11.     // 「通信」这里接收Worker线程文件中发出的消息
  12.     workerThread.onmessage = ((event: MessageEvents) => {
  13.       const type = event.data.type as number;
  14.       if (type === 2) { // 这里是的匹配工作,是由worker工作线程通过workerPort.postMessage({type: 2, ..})发来的信息。
  15.         console.error("打印日志,worker主线程收到worker工作线程发来的消息:", event.data.value)
  16.       }
  17.     })
  18.     workerThread.onerror = ((event: ErrorEvent) => {})
  19.         // 「通信」这里向Worker线程文件发送消息
  20.     workerThread.postMessage({ 'type': 1, value: '「主线程数据包」' })
  21.   }
  22.   build() {
  23.     Stack({ alignContent: Alignment.Top }) {
  24.     ... 省略...
复制代码
「利用要点」「注意事项」
   worker.workerPort的实例对象是主线程发送消息或接收主线程发送的消息。worker.workerPort就是用来执行耗时使命工作用的。
  workerPort.onmessage方法,监听并接收主线程发出的消息。
workerPort.postMessage方法,向主线程发送消息。
  1. /// ets/workers/MyTestWorker1.ets
  2. import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
  3. const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
  4. /**
  5. * Defines the event handler to be called when the worker thread receives a message sent by the host thread.
  6. * The event handler is executed in the worker thread.
  7. *
  8. * @param e message data
  9. */
  10. workerPort.onmessage = (e: MessageEvents) => {
  11.   const type = e.data.type as number;
  12.   // 耗时操作,逻辑处理等
  13.   if (type === 1) {
  14.     console.error("打印日志,worker工作线程收到主线程发来的消息:", e.data.value)
  15.     workerPort.postMessage({type: 2, value: '「工作线程数据包」'}) // 通知zhuworker线程
  16.   }
  17. }
  18. ...省略...
复制代码
运行上面源码执行效果

TaskPool创建|利用

TaskPool在创建及利用上较Worker则太过简单了,等同于拿来即用。
进入TaskPool#execute方法源码,(即从下面截图中)看到在taskpool命名空间中界说有三个重载的方法在提供利用

  1. function execute(func: Function, ...args: Object[]): Promise<Object>;
  2. function execute(task: Task, priority?: Priority): Promise<Object>;
  3. function execute(group: TaskGroup, priority?: Priority): Promise<Object[]>;
复制代码
  针对这三种重载方法创建执行方式,没有太合适且简单易懂的示例代码做演示。就现场手动码一段精确代码演示下
  1. import { taskpool } from '@kit.ArkTS';
  2. /**工作任务,用来执行耗时操作:CPU/IO密集型*/
  3. // 「提示」1,需要加装饰器@Concurrent;2,需要function关键字;3,需要声明在@Component外;4,返回值须要是值类型。
  4. @Concurrent
  5. async function taskMethod1 (): Promise<number> {
  6.   // return Promise.resolve(9) // 不支持。Promise.resolve仍是Promise,其状态是pending,无法作为返回值使用。
  7.   return 1; // 返回值仅能是「携带值res引用,如const res = [1,2,3]」值类型
  8. }
  9. /**工作任务,用来执行耗时操作:CPU/IO密集型*/
  10. @Concurrent
  11. async function taskMethod2 (): Promise<string>  {
  12.   return 'hello world'; // 返回值仅能是「携带值res引用,如const res = [1,2,3]」值类型
  13. }
  14. @Entry
  15. @Component
  16. struct HomePage {
  17.    onPageShow(): void {
  18.      this.execute()
  19.   }
  20.   async execute() {
  21.     // function execute(func: Function, ...args: Object[]): Promise<Object>;
  22.     const resultMethod = await taskpool.execute(taskMethod1, taskMethod2)
  23.     // function execute(task: Task, priority?: Priority): Promise<Object>;
  24.     const task1 = new taskpool.Task('任务名称「非必填」', taskMethod1)
  25.     const resultTask = await taskpool.execute(task1, taskpool.Priority.HIGH)
  26.     // function execute(group: TaskGroup, priority?: Priority): Promise<Object[]>;
  27.     const group1 = new taskpool.TaskGroup('定义任务组名称「非必填」') // TaskGroup有两个构造方法,一个无参,一个有字符串入参
  28.     group1.addTask(taskMethod1) // 向任务组中添加任务方法的引用
  29.     group1.addTask(taskMethod2) // 向任务组中添加任务方法的引用
  30.     const resultGroup = await taskpool.execute(group1, taskpool.Priority.HIGH)
  31.     console.error('打印输出:', resultMethod, resultTask, resultGroup)
  32.   }
  33.   build() {
  34.     Stack({ alignContent: Alignment.Top }) {
  35. ...省略...
复制代码
「注意事项」「提示」实现使命的函数需要①利用装饰器@Concurrent标注,且②仅支持在.ets文件中利用③方法需要function修饰
假如在@Component内部创建使命,会提示报错The @Concurrent decorator can decorate only common functions and async functions. <ArkTSCheck>,因此④需要在@Component外部创建才可以
「注意事项」「别的」假如在利用装饰器@Concurrent标注的使命方法中调用了某类的方法类.方法名(args) 或 类实例.方法名(args),①声明类须利用装饰器@Sendable标注。假如不是②通过import方式导入利用,则提示报错:Only imported variables and local variables can be used in @Concurrent decorated functions. <ArkTSCheck>


  • 未利用import引入时,错误提示截图图示

精确通过import方式引入并调用方法的方式,如下。
  1. /**声明类,任务方法中将引用该类SendableTask,使用装饰器@Sendable修饰*/
  2. @Sendable
  3. export default class SendableTask {
  4.   private static instance: SendableTask = new SendableTask();
  5.   static getInstance(): SendableTask {
  6.     // 获取单例
  7.     return SendableTask.instance;
  8.   }
  9.   // 声明模拟一个方法
  10.   static oneSyncMethod(): number {
  11.     return 10;
  12.   }
  13.   oneMethod(): number {
  14.     return 2;
  15.   }
  16. }
复制代码
下面的截图中张贴了具体的调用逻辑,如安在使命方法@Concurrent async function taskMethod1中对利用外部类方法SendableTask.oneSyncMethod调用,

接下来跑下DEMO步伐,执行TaskPool多线程并发代码,运行效果输出如截图所示:

  1. 08-14 14:43:34.406  45415-45415  A03D00/JSAPP  pid-45415   E   打印输出: 10 10 10,hello world
复制代码
「总结」从截图debug显示及输出日记效果,可以发现并得出结论,taskpool.execute执行并发使命的三种重载方法,在执行时,各自输出的效果泉源~如下
  1. // 输出结果值来源:结果值为func方法执行结果。
  2. function execute(func: Function, ...args: Object[]): Promise<Object>;
  3. // 输出结果值来源:结果值为task中调用执行的func方法执行结果。
  4. function execute(task: Task, priority?: Priority): Promise<Object>;
  5. // 输出结果值来源:结果值是个数组,数组中每个元素值,为每个task中调用执行的func方法执行结果。
  6. function execute(group: TaskGroup, priority?: Priority): Promise<Object[]>;
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

东湖之滨

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表