承诺导致崩溃的网络工作者
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 慢得多。
重现步骤:
- 此处未提及的代码的另一部分创建了一个
Mosaic
对象。然后,我们对该 Mosaic 对象调用 build()
。
- 崩溃
我想我误解了工人的工作方式。这是正确的方法吗?如何修复代码使其不再崩溃?
谢谢!
问题是您在嵌套的 for
循环中调用 makeTile
,其中 makeTile
创建 worker
。您正在创建 950 Worker
个实例。每个 Worker
个实例调用 postMessage
。这就是浏览器崩溃的原因。
您需要调整脚本以处理承诺数组,而不是单个 Promise
。 worker.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
。然后用包含处理后数据的返回数组做一些事情。
我的代码的预期工作流程是从 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 慢得多。
重现步骤:
- 此处未提及的代码的另一部分创建了一个
Mosaic
对象。然后,我们对该 Mosaic 对象调用build()
。 - 崩溃
我想我误解了工人的工作方式。这是正确的方法吗?如何修复代码使其不再崩溃?
谢谢!
问题是您在嵌套的 for
循环中调用 makeTile
,其中 makeTile
创建 worker
。您正在创建 950 Worker
个实例。每个 Worker
个实例调用 postMessage
。这就是浏览器崩溃的原因。
您需要调整脚本以处理承诺数组,而不是单个 Promise
。 worker.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
。然后用包含处理后数据的返回数组做一些事情。