承诺中的Web工作人员导致崩溃 [英] Web workers inside promise causing crash

查看:167
本文介绍了承诺中的Web工作人员导致崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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



这是需要 getData的代码块 function:

  class Mosaic {
// ...
build (){
for(let y = 0; y< canvas.height; y + = options.tileHeight){
for(let x = 0; x< canvas.width; x + = options.tileWidth){
// ...这里的一些代码
// imgContext:包含图像的画布上下文
let area = imgContext.getImageData(x,y,w, H);
this.getData(area,x,y)
.then((data)=> {
//用数据
做某事)
// ...
}
}
// ...
}

// ...
}

这是getData函数:

  getData(data,x,y){
返回新的Promise((resolve,reject)=> {
let worker = new Worker('js / worker .js');
worker.onmessage =(e)=> {
let hex = e.data;
let img = new Image();
let loc = (
十六进制:十六进制,
x:x,
y:
解析({
十六进制:十六进制,
x: y
});
}
img.src = loc;
}
worker.postMessage(data);
});

js / worker.js

  self.addEventListener('message',function(e){
let hex = ImageUtil.getHex(e.data); //另一个函数
self.postMessage(hex );
self.close();
},false);

class ImageUtil {
static getHex(imgData){
let data = imgData.data; (让i = 0; i< data.length; i + = 4)
让r = 0,
g = 0,
b = 0,

{
//在这里计数rgb
}

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. 崩溃

我想我误解了工人的工作方式。这是正确的方式,我该如何修复代码,以防止崩溃?



谢谢!

解决方案

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



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



您可以在循环之前为循环创建一个数组,将数据作为数组传递给 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 / /)//在`makeTile`中做必要的修改
.then(function(tiles){
//用`tiles`数组

})
}))

移动

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

超出 makeTile()或创建逻辑,以便该呼叫只进行一次。同样,在 worker.js 中,调整脚本以处理一组数据,而不是一个单一的值。



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



解决方案的要点是重构您的代码库以处理数组和承诺数组;主线程和 worker.js ;目标是最多一次调用 worker.js 一次<更改
事件< input type =file> 元素。如果单个消息发布到工作者,并且单个消息事件预计来自 Worker 。然后对包含处理过的数据的返回数组进行处理。


The expected workflow of my code is getting the data from getData.

getData calls the worker that will do ImageUtil.getHex on the input. ImageUtil.getHex is a heavy function that needs to iterate every pixel of an image area area, so that's why I want to create it runs in the background and done in multithreading. The function is also independent, which I guess is a good candidate to put in the worker.

This is the chunk of code that needs the getData function:

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
            });
        //... 
      }
    }
  // ...
  }

  // ...
}

this is the getData function:

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;
  }
}

The problem is, when ran, it makes the browser crash. Even if it's not crashing, the performance is much slower than without using worker.

Steps to reproduce:

  1. The other part of the code that isn't mentioned here create a Mosaic object. Then, we call build() on that Mosaic object.
  2. Crash

I think I misunderstood the way of workers work. Is it the right way, and how do I fix the code so it won't crash anymore?

Thanks!

解决方案

The issue is that you are calling makeTile within a nested for loop, where makeTile creates worker. You are creating 950 Worker instances. Each Worker instance calls postMessage. That is the reason the browser is crashing.

You need to adjust you scripts to handle arrays of promises, instead of a single Promise. worker.js should be called once, not 950 times.

You can create an array before the for loops, pass the data as an array to 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]))
    }
  };

then after the for loops use Promise.all() to process the data

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

   })
}))

Move

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

outside of makeTile(), or create logic so that the call is only made once.

Similarly, at worker.js, adjust the script to handle an array of data, instead of a single value.

When message event is fired at main thread, process the data as an array of values.

The gist of the solution is to refactor your code base to handle arrays, and arrays of promises; both at main thread and at worker.js; with the object being to call worker.js at most once at change event of <input type="file"> element. Where a single message is posted to Worker, and single message event is expected from Worker. Then do stuff with the returned array containing the processed data.

这篇关于承诺中的Web工作人员导致崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆