鸿蒙多线程 worker的使用以及问题

7 阅读3分钟

1.worker 的创建

 首先创建 worker 文件;右键 ->选择 worker即可;创建对应的 worker 文件;

在页面中创建 worker 实例;如下:

private workerPort: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/Worker.ets')

注意:添加的路径是如下格式:[moduleName]/ets/[文件相对路径];同时也支持设置优先级等参数;如下:

new worker.ThreadWorker('entry/ets/workers/Worker.ets',{priority:100,name:'下载 worker',shared:true})

worker子线程:

const workerPort: ThreadWorkerGlobalScope = worker.workerPort;

workerPort.onmessage = (event: MessageEvents) => {
   // event.data//用于接收宿主发送过来的数据
}

//当工作线程收到无法反串化的消息时调用。事件处理程序在 Worker 线程中执行
workerPort.onmessageerror = (event: MessageEvents) => {
};
//当工作者执行过程中发生异常时调用。事件处理程序在 Worker 线程中执行。
workerPort.onerror = (event: ErrorEvent) => {
};

宿主线程接收 worker子线程返回的数据如下:

  aboutToAppear(): void {
    this.workerPort.onmessage = (event: MessageEvents) => {
         //在这里用于接收子线程传递过来的数据
        }
  //同样可以通过 workerPort监听错误,退出等监听
  }

宿主线程关闭 worker 线程,这个地方是必须的。worker 的线程的生命周期开发者控制;

  aboutToDisappear(): void {
    try {
      this.workerPort.terminate()
    } catch (error) {
    }
  }

子线程或者宿主线程发送数据到对方:

let info = new UserModel('小明', '20')
this.workerPort.postMessage(info)

2.worker实现数据的传递

数据 model 代码

export class UserModel {
  name: string
  age: string

  constructor(name: string, age: string) {
    this.name = name
    this.age = age
  }
  printUserInfo() {
    console.log('----------- > worker UserModel name ' + this.name + " age " + this.age)
  }
}

将数据传递到 worker 子线程:

try {
  let info = new UserModel('小明', '20')
  this.workerPort.postMessage(info)
} catch (error) {
  console.log('----------- > worker page ' + error)
}

worker 子线程接收数据代码如下:

workerPort.onmessage = (event: MessageEvents) => {
  try {
    const user = event.data as UserModel
    console.log('----------- > worker ets ' + JSON.stringify(user))
    //直接调用类实例中的方法
    user.printUserInfo()
  } catch (error) {
    console.log('----------- > worker ets catch ' + error)
  }
};
结果日志:
----------- > worker ets {"name":"小明","age":"20"}
----------- > worker ets catch TypeError: undefined is not callable

        结果同 taskpool 在的子线程执行一致;在子线程传递数据时,采用的是序列化传递方式;相当于仅仅将宿主对象的值给了 worker 子线程类变量,并没有序列化类实例中的方法。因此出现未定义异常;

        如何解决上述问题呢?其实就是在 model 上添加注解@Sendable;如下:

@Sendable
export class UserModel {
}
//再次执行结果:
----------- > worker ets {"name":"小明","age":"20"}
----------- > worker UserModel name 小明 age 20

那么在子线程和宿主线程中的对象是一个吗?以及在子线程将数据更改后,宿主线程的数据会更改吗?验证代码如下:

worker 线程:
workerPort.onmessage = (event: MessageEvents) => {
  try {
    const user = event.data as UserModel
    console.log('----------- > worker worker onmessage ' + JSON.stringify(user) + " .... hash " + util.getHash(user).toString())
    user.name = '大明'
    workerPort.postMessage(user)
  } catch (error) {}


//宿主线程
 try {
   let info = new UserModel('小明', '20')
   console.log('----------- > worker page hash ' + util.getHash(info))
   this.workerPort.postMessage(info)
   setTimeout(() => {
     console.log('----------- > worker page timeout ' + json.stringify(info) +".... hash "+util.getHash(info).toString())
   }, 1000)
 } catch (error) {
   console.log('----------- > worker page ' + error)
 }

输入日志:

13161-13161   I     ----------- > worker page hash 1720174752
13161-13417   I     ----------- > worker worker onmessage {"name":"小明","age":"20"} .... hash 1720174752
13161-13161   I     ----------- > worker page onmessage {"name":"大明","age":"20"} ... hash 1720174752
13161-13161   I     ----------- > worker page timeout {"name":"小明","age":"20"}.... hash 1720174752

结论:在传递添加@Sendable 的 model 时,宿主线程和 worker 线程的 hash值是一样的,这里也就是进行的内存地址传递;

但是在子线程更改值后,主线程的变量值并不会跟随改变;因此想要获取改变后的值,可以通过在宿主线程接收值来监听改变;(这个地方和 java 的内存共享不太一样;也没有搞明白原因)

worker线程中单例使用,以及对应问题:

单例 model

"use shared"
@Sendable
export class UserInfo {
  name: string = ''
  age: string = ''
   money: number = 0
  //添加异步锁
  lock: ArkTSUtils.locks.AsyncLock = new ArkTSUtils.locks.AsyncLock()
  private static readonly instance: UserInfo = new UserInfo()

  static getInstance() {
    return UserInfo.instance
  }

  private constructor() {}

  getNameInfo() {
    return `名字 .... ${this.name}`
  }
  addMoney() {
    this.lock.lockAsync(() => {
      this.money++
    }).catch(() => {
    })
  }
}

export const userInfo: UserInfo = UserInfo.getInstance()

宿主线程调用:

 try {
   let info = userInfo
   info.name = '小明'
   info.age = '20'
   console.log('----------- > worker page ' + util.getHash(info).toString())
   this.workerPort.postMessage(info)
   setTimeout(() => {
     console.log('----------- > worker page update ' + json.stringify(info))
   }, 1000)
 } catch (error) {
   console.log('----------- > worker page ' + error)
 }

worker 线程

workerPort.onmessage = (event: MessageEvents) => {
    try {
         const  user =  event.data as UserInfo
          console.log('----------- > worker ets ' + JSON.stringify(user) + " .... " + util.getHash(user).toString())
          //直接获取实例
          user.name = '大明'
          console.log('----------- > worker ets singleton update ' + JSON.stringify(user) )
          console.log('----------- > worker ets singleton ' + JSON.stringify(userInfo) + " .... " + util.getHash(userInfo).toString())
    } catch (error) {
          console.log('----------- > worker ets catch ' + error)
    }
}

出现问题日志:

   // 如果传递的数据内部包含异步锁,将无法正常传递数据,报如下异常;
BusinessError: An exception occurred during serialization, failed to serialize message.
Serialize error: Serialize don't support object type: NativePointer

原因是在传递的 model 中存在无法序列化的内容;其实就是异步锁:

lock: ArkTSUtils.locks.AsyncLock = new ArkTSUtils.locks.AsyncLock()

    没有找到原因:应该是现在鸿蒙还不支持 worker 线程,传递携带异步锁的 model;如果存在需要异步锁的地方尽量使用 taskpool;

注释异步锁代码后执行结果:

5589-5589     I     ----------- > worker page 1016019831
5589-5840     I     ----------- > worker ets {"name":"小明","age":"20"} .... 1016019831
5589-5840     I     ----------- > worker ets singleton update {"name":"大明","age":"20"}
5589-5840     I     ----------- > worker ets singleton {"name":"小明","age":"20"} .... 1016019831
5589-5589     I     ----------- > worker page update {"name":"小明","age":"20"}

发现在worker中针对 model 设置@Sendable +"use shared" 在子线程更改值后,在主线程的对象是没有更新的,设置延时获取同样没有更新;但是对象的 hash 值却是相同的;

**注意:**这个地方同 taskpool 不一样;在 taskpool 中,在 model 设置了内存共享后,如果子线程更改,在宿主线程值同样改变;

代码如下:

let user = new UserModel('小明', '20')
console.log(`--------- > taskpool 宿主线程 hash ${util.getHash(user)
  .toString()} ... ${JSON.stringify(user)}`)
taskpool.execute(verify,user).catch(() => {})
setTimeout(()=>{
  console.log(`--------- > taskpool result hash ${util.getHash(user)
    .toString()} ... update  ${JSON.stringify(user)}`)
},1000)

子线程代码:

@Concurrent
function verify(user: UserModel) {
  console.log(`--------- > taskpool Concurrent hash ${util.getHash(user)
    .toString()} ...   ${JSON.stringify(user)}`)
  user.name = '大明'

  console.log(`--------- > taskpool Concurrent  ... update  ${JSON.stringify(user)}`)
}

执行结果:

5480-5480     I     --------- > taskpool 宿主线程 hash 1198510185 ... {"name":"小明","age":"20"}
5480-5613     I     --------- > taskpool Concurrent hash 1198510185 ...   {"name":"小明","age":"20"}
5480-5613     I     --------- > taskpool Concurrent  ... update  {"name":"大明","age":"20"}
5480-5480     I     --------- > taskpool result hash 1198510185 ... update  {"name":"大明","age":"20"}

参考文档: developer.huawei.com/consumer/cn…