如何让一个webworker同时做多个任务?

How to let a webworker do multiple tasks simultaneously?

我正在尝试让 Web-Worker 管理其状态,同时为多个异步请求提供服务。

worker.ts 文件

let a =0; //this is my worker's state

let worker=self as unknown as Worker;

worker.onmessage =(e)=>{
    console.log("Rec msg", e.data);

    if(e.data === "+1"){
        setTimeout(()=>{
            a=a+1;
            worker.postMessage(a);
        },3000);
    }else if(e.data=== "+2"){
        setTimeout(()=>{
            a=a+2;
            worker.postMessage(a);
        },1000)
    }
}

这是我的主文件:main.ts

let w =new Worker("./worker.ts", {type: "module"})

let wf =async (op: string)=>{
    w.postMessage(op);
    return new Promise<any>((res,rej)=>{
        w.onmessage=res;
    });
}

(async()=>{
    let f1 = await wf("+1");
    console.log("f1",f1.data);
})();

(async()=>{
    let f2 = await wf("+2");
    console.log("f2",f2.data);
})()

只返回f2,丢失f1。 我已经使用超时来模拟说一些由工作人员自己完成的异步任务。

如何同时收到 f1f2

您的问题是您正尝试采用基于事件的 API 并将其用作基于 Promise 的事件,但事件可能会触发多次,而 Promise 应该只解决一次。

Worker和主线程之间的通信是通过发送和接收消息来进行的,但是默认情况下这些消息之间没有一对一的关系。通信的两端(端口)将简单地堆叠传入的消息,并在它们有时间时按顺序处理它们。

在您的代码中,f1 的主线程 worker.onmessage 处理程序已被第二次调用 f2 同步覆盖(一个微任务之后,但对我们来说仍然是同步的) .
您可以使用 addEventListener 方法附加您的事件,至少这样它不会被覆盖。但即便如此,当第一个 message 事件将在 worker 上触发时,两个处理程序都会认为它确实到达了自己的消息,而实际上它是 f2所以这不是你需要的...

你需要的是建立一个通信协议,让双方都能识别每个任务。例如,您可以使用包含 .UIID 成员的对象包装所有任务的数据,确保两端以这种方式包装他们的消息,然后从主线程检查 UUID 以解析适当的 Promise.

但是实施和使用起来会有点复杂。


我个人最喜欢的方法是创建一个新的 MessageChannel per task. If you don't know this API, I invite you to read 来解释基础知识。

因为我们确定通过这个 MessageChannel 的唯一消息是 Worker 对我们发送给它的一个任务的响应,我们可以等待它就像一个承诺。

我们所要做的就是确保在工作线程中我们通过传输的端口而不是全局范围进行响应。

const url = getWorkerURL();
const worker = new Worker(url)

const workerFunc = (op) => {
  // we create a new MessageChannel
  const channel = new MessageChannel();
  // we transfer one of its ports to the Worker thread
  worker.postMessage(op, [channel.port1]);

  return new Promise((res,rej) => {
    // we listen for a message from the remaining port of our MessageChannel
    channel.port2.onmessage = (evt) => res(evt.data);
  });
}

(async () => {
  const f1 = await workerFunc("+1");
  console.log("f1", f1);
})();

(async () => {
  const f2 = await workerFunc("+2");
  console.log("f2", f2);
})()


// SO only
function getWorkerURL() {
  const elem = document.querySelector( '[type="worker-script"]' );
  const script = elem.textContent;
  const blob = new Blob( [script], { type: "text/javascript" } );
  return URL.createObjectURL( blob );
}
<script type="worker-script">
let a = 0;
const worker = self;

worker.onmessage = (evt) => {
  const port = evt.ports[0]; // this is where we will respond
  if (evt.data === "+1") {
    setTimeout(() => {
      a = a + 1;
      // we respond through the 'port'
      port.postMessage(a);
    }, 3000);
  }
  else if (evt.data === "+2") {
    setTimeout(() => {
      a = a + 2;
      // we respond through the 'port'
      port.postMessage(a);
    }, 1000)
  }
};
</script>