如何让Web Workers在执行计算时接收新数据?

Kad*_*BOT 2 javascript sorting interrupt worker web-worker

我想使用Web Workers对数组进行排序.但是这个数组可能会随着时间的推移接收到新值,而worker仍在执行sort函数.

所以我的问题是,如何在收到新项目后"停止"工作者的排序计算,这样它就可以对该项目执行数组排序,同时仍然保留已经进行的排序?

例:

let worker = new Worker('worker.js');
let list = [10,1,5,2,14,3];
worker.postMessage({ list });
setInterval(() => worker.postMessage({ num: SOME_RANDOM_NUM, list }), 100);

worker.onmessage = event => {
  list = event.data.list;
}
Run Code Online (Sandbox Code Playgroud)

让我们说,我已经过了50岁,工人在此之前在排序方面取得了一些进展,现在我有这样的事情: [1, 2, 3, 10, 5, 14, 50].这意味着排序在索引处停止3.所以我将这个new数组传递回worker,因此它可以继续从位置进行排序3.

我怎样才能实现这一点,因为没有办法暂停/恢复网络工作者?

Kai*_*ido 5

即使Worker工作在主页之外的其他线程上,并且因此可以连续运行而不阻塞UI,它仍然在单个线程上运行.

这意味着在排序算法完成之前,Worker将延迟执行消息事件处理程序; 它与主线程一样被阻止.

即使你从这个工人内部使用了另一个工人,问题也是一样的.

唯一的解决方案是使用一种生成器函数作为分类器,并不时地产生它,以便事件可以执行.

但这样做会大大减慢排序算法的速度.

为了使它更好,您可以尝试挂钩到每个事件循环,这要归功于MessageChannel对象:您在一个端口进行通信并在下一个Event循环中接收消息.如果再次与另一个端口通话,那么每个Event循环都有自己的挂钩.

现在,最好的方法是在每个Event循环中运行一个好的批处理,但是对于demo,我只调用我们的生成器函数的一个实例(我从这个Q/A中借用)

const worker = new Worker(getWorkerURL());
worker.onmessage = draw;

onclick = e =>     worker.postMessage(0x0000FF/0xFFFFFF); // add a red pixel

// every frame we request the current state from Worker
function requestFrame() {
  worker.postMessage('gimme a frame');
  requestAnimationFrame(requestFrame);
}
requestFrame();

// drawing part
const ctx = canvas.getContext('2d');
const img = ctx.createImageData(50, 50);
const data = new Uint32Array(img.data.buffer);
ctx.imageSmoothingEnabled = false;

function draw(evt) {
  // converts 0&1 to black and white pixels
  const list = evt.data;
  list.forEach((bool, i) =>
    data[i] = (bool * 0xFFFFFF) + 0xFF000000
  );
  ctx.setTransform(1,0,0,1,0,0);
  ctx.clearRect(0,0,canvas.width,canvas.height);
  ctx.putImageData(img,0,0);
  // draw bigger
  ctx.scale(5,5);
  ctx.drawImage(canvas, 0,0);
}

function getWorkerURL() {
  const script = document.querySelector('[type="worker-script"]');
  const blob = new Blob([script.textContent]);
  return URL.createObjectURL(blob);
}
Run Code Online (Sandbox Code Playgroud)
body{
  background: ivory;
}
Run Code Online (Sandbox Code Playgroud)
<script type="worker-script">
// our list
const list = Array.from({length: 2500}).map(_=>+(Math.random()>.5));
// our sorter generator
let sorter = bubbleSort(list);
let done = false;
/* inner messaging channel */
const msg_channel = new MessageChannel();
// Hook to every Event loop
msg_channel.port2.onmessage = e => {
  // procede next step in sorting algo
  // could be a few thousands in a loop
  const state = sorter.next();
  // while running
  if(!state.done) {
    msg_channel.port1.postMessage('');
    done = false;
  }
  else {
    done = true;
  }
}
msg_channel.port1.postMessage("");

/* outer messaging channel (from main) */
self.onmessage = e => {
  if(e.data === "gimme a frame") {
    self.postMessage(list);
  }
  else {
    list.push(e.data);
    if(done) { // restart the sorter
      sorter = bubbleSort(list);
      msg_channel.port1.postMessage('');
    }
  }
};

function* bubbleSort(a) { // * is magic
  var swapped;
  do {
    swapped = false;
    for (var i = 0; i < a.length - 1; i++) {
      if (a[i] > a[i + 1]) {
        var temp = a[i];
        a[i] = a[i + 1];
        a[i + 1] = temp;
        swapped = true;
        yield swapped; // pause here
      }
    }
  } while (swapped);
}
</script>
<pre> click to add red pixels</pre>
<canvas id="canvas" width="250" height="250"></canvas>
Run Code Online (Sandbox Code Playgroud)