comlink
comlink copied to clipboard
worker to worker communication
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
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()
I expanded on @MontolioV 's ideas here (using Shared Workers as an example): https://github.com/dfbaskin/shared-worker-service-invocation
#294 has an example of using Comlink.createEndpoint
to indirectly use a MessageChannel. Think this issue can be closed now.