承诺导致崩溃的网络工作者

Web workers inside promise causing crash

我的代码的预期工作流程是从 getData 获取数据。

getData 调用将对输入执行 ImageUtil.getHex 的工作程序。 ImageUtil.getHex 是一个繁重的函数,需要迭代图像区域的每个像素 area,所以这就是为什么我想创建它在后台运行并在多线程中完成。该功能也是独立的,我想这是一个很好的候选人。

这是需要 getData 函数的代码块:

class Mosaic {
    // ...
  build() {
    for (let y = 0; y < canvas.height; y += options.tileHeight) {
      for (let x = 0; x < canvas.width; x += options.tileWidth) {
        //... some code here
        // imgContext: a context of a canvas containing the image
        let area = imgContext.getImageData(x, y, w, h);
        this.getData(area, x, y)
          .then((data) => {
              // do something with data
            });
        //... 
      }
    }
  // ...
  }

  // ...
}

这是 getData 函数:

getData(data, x, y) {
  return new Promise((resolve, reject) => {
    let worker = new Worker('js/worker.js');
    worker.onmessage = (e) => {
      let hex = e.data;
      let img = new Image();
      let loc = `image/${hex}`
      img.onload = (e) => {
        resolve({
          hex: hex,
          x: x,
          y: y
        });
      }
      img.src = loc;
    }
    worker.postMessage(data);
  }); 

js/worker.js

self.addEventListener('message', function(e) {
  let hex = ImageUtil.getHex(e.data); // another function
  self.postMessage(hex);
  self.close();
}, false);

class ImageUtil {
  static getHex(imgData) {
    let data = imgData.data;
    let r = 0,
        g = 0,
        b = 0,

    for (let i = 0; i < data.length; i += 4) {
      // count rgb here
    }

    let count = data.length / 4;
    r = ("0" + (Math.floor(r / count)).toString(16)).slice(-2);
    g = ("0" + (Math.floor(g / count)).toString(16)).slice(-2);
    b = ("0" + (Math.floor(b / count)).toString(16)).slice(-2);
    let hex = `${r}${g}${b}`;
    return hex;
  }
}

问题是,当运行时,它会使浏览器崩溃。即使不崩溃,性能也比不使用 worker 慢得多。

重现步骤:

  1. 此处未提及的代码的另一部分创建了一个 Mosaic 对象。然后,我们对该 Mosaic 对象调用 build()
  2. 崩溃

我想我误解了工人的工作方式。这是正确的方法吗?如何修复代码使其不再崩溃?

谢谢!

问题是您在嵌套的 for 循环中调用 makeTile,其中 makeTile 创建 worker。您正在创建 950 Worker 个实例。每个 Worker 个实例调用 postMessage。这就是浏览器崩溃的原因。

您需要调整脚本以处理承诺数组,而不是单个 Promiseworker.js 应该调用一次,而不是 950 次。

您可以在 for 循环之前创建一个数组,将数据作为数组传递给 Promise.resolve()

  var arr = [];
  for (let y = 0; y < canvas.height; y += options.tileHeight) {
    for (let x = 0; x < canvas.width; x += options.tileWidth) {
      let areaData = imgContext
                     .getImageData(x, y, options.tileWidth, options.tileHeight);
      arr.push(Promise.resolve([areaData, x, y]))
    }
  };

然后在 for 循环之后使用 Promise.all() 处理数据

Promise.all(arr.map(function(tile) {
   this.makeTile(/* tile data here */) // make necessary changes at `makeTile`
   .then(function(tiles) {
     // do stuff with `tiles` array

   })
}))

移动

let worker = new Worker('worker.js');

makeTile() 之外,或者创建逻辑以便只调用一次。

同样,在 worker.js,调整脚本以处理一组数据,而不是单个值。

当在主线程中触发 message 事件时,将数据作为值数组处理。

解决方案的要点是重构您的代码库以处理数组和承诺数组;在主线程和worker.js;目标是在 change 最多调用一次 worker.js <input type="file"> 元素的事件。其中单个 message 被发布到 Worker,并且单个 message 事件预计来自 Worker。然后用包含处理后数据的返回数组做一些事情。