comlink icon indicating copy to clipboard operation
comlink copied to clipboard

worker to worker communication

Open hyusetiawan opened this issue 4 years ago • 3 comments

is there an example of worker to worker communication using MessageChannel? The concept of message channel is brought up many times in the docs but I can't seem to find/figure out how to use it for worker to worker through Comlink

hyusetiawan avatar Apr 18 '20 10:04 hyusetiawan

If anyone is still interested in this topic. Consider that in this example I use https://github.com/webpack-contrib/worker-loader , so some constructors are just a syntax sugar.

We can communicate between main thread and each worker, and in the same time between workers. But we need a dedicated MessageChannel for each case: main <-> Render (implicit MessageChannel) main <-> Calc (implicit MessageChannel) Render <-> Calc (explicit MessageChannel) Keep in mind that each message channel is isolated, consider where you push and retrieve the data.

Render.worker.ts

import * as Comlink from 'comlink'
import '../other'
import { Renderer } from './workerClasses'

Comlink.expose(new Renderer())

Calc.worker.ts

import * as Comlink from 'comlink'
import '../other'
import { Calc } from './workerClasses'

Comlink.expose(new Calc())

workerClasses.ts

import * as Comlink from 'comlink'
import rnd from 'random-int'

/**
 * Do not store Comlink stubs as properties of an object that is exposed,
 * the library can't handle that case.
 */
let remoteRender: Comlink.Remote<Renderer> | null = null
let remoteCalc: Comlink.Remote<Calc> | null = null

export class Renderer {
  counter = 0

  async bindPort(port: MessagePort) {
    Comlink.expose(this, port)
    remoteCalc = Comlink.wrap<Calc>(port)
  }

  async inc() {
    this.counter++
    await remoteCalc?.notify('Im from Render')
    console.log(await remoteCalc?.counter)
  }
}

export class Calc {
  counter = 0

  async bindPort(port: MessagePort) {
    Comlink.expose(this, port)
    remoteRender = Comlink.wrap<Renderer>(port)
  }
  async inc() {
    this.counter++
    await remoteRender?.notify('Im from Calc')
  }
  notify(message: string) {
    console.log(message)
  }
}

main.ts

import * as Comlink from 'comlink'
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import CalcWorker from './workers/Calc.worker'
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import RendererWorker from './workers/Render.worker'
import { Calc, Renderer } from './workers/workerClasses'

async function init() {
  const rendererLocal: Worker = new RendererWorker()
  const calcLocal: Worker = new CalcWorker()

  const messageChanel = new MessageChannel()
  const rendererPort = messageChanel.port1
  const calcPort = messageChanel.port2

  const renderer = Comlink.wrap<Renderer>(rendererLocal)
  const calculator = Comlink.wrap<Calc>(calcLocal)

  await renderer.bindPort(Comlink.transfer(calcPort, [calcPort]))
  await calculator.bindPort(Comlink.transfer(rendererPort, [rendererPort]))

  console.log(await renderer.counter)
  await renderer.inc()
}

init()

MontolioV avatar Apr 27 '21 10:04 MontolioV

I expanded on @MontolioV 's ideas here (using Shared Workers as an example): https://github.com/dfbaskin/shared-worker-service-invocation

dfbaskin avatar Sep 26 '23 00:09 dfbaskin

#294 has an example of using Comlink.createEndpoint to indirectly use a MessageChannel. Think this issue can be closed now.

BrianHung avatar Dec 21 '23 09:12 BrianHung