发生这种情况是因为draw()
不等待 WebWorker 的返回值并使用requestAnimationFrame
. 如果您想要连续过滤图像,则必须draw
在应用过滤器后调用:
filterWorker.addEventListener('message', function(e) {
ctx.putImageData(e.data, 0, 0);
requestAnimationFrame(draw);
}, false);
但是,如果过滤器需要很长时间,很可能draw
会在早期用彩色数据填充画布以注意到变化(我能够在当前版本中看到带有 FF17 的黑白版本)。
我的代码有什么问题?
在操作顺序很重要的场景中,您正在使用一种用于并行计算的工具:您希望在显示之前对每个图像应用过滤器。让我引用 MDN:
关于线程安全
Worker接口产生了真正的操作系统级线程,细心的程序员可能会担心,如果您不小心,并发可能会在您的代码中产生“有趣”的效果。
但是,由于 web Worker 已经仔细控制了与其他线程的通信点,因此实际上很难引起并发问题。无法访问非线程安全组件或 DOM。而且您必须通过序列化对象将特定数据传入和传出线程。所以你必须非常努力地在你的代码中引起问题。
与真正的操作系统级线程一样,除非您使用某种阻塞机制,否则无法对执行顺序做出任何假设,但这不包含在 JavaScript 中。所以你需要在同一个线程中绘制和操作图像。
进一步说明
顺序案例
看看下面的图片:
这就是技术演示中基本上发生的事情。draw()
复制视频并立即过滤图像:
function draw(v,c,w,h,filter) {
if(v.paused || v.ended) return false;
c.drawImage(v,0,0,w,h);
if (typeof filter === 'undefined') {
// variable is undefined
} else {
var idata = c.getImageData(0,0,w,h);
newdata = filterdata(idata,filter);
c.putImageData(newdata,0,0);
}
setTimeout(draw,20,v,c,w,h,filter);
}
如您所见,drawImage
以下过滤器的调用之间没有间隙。生成的图片显示大约 20 毫秒。即使过滤器需要 1 毫秒,用户也可能不会注意到这一点。
平行案例
现在它变得有点复杂了。黑化文本框是直接修改画布的方法,白框表示触发事件或注册超时,黑框表示超时执行或事件处理:
现在,会发生什么?draw
仍然大约每 20 毫秒调用一次并复制视频。然后它向工人发布一条消息。但是,没有说明何时发送此消息。它可以立即放在worker的事件循环中(如图所示),也可以放在“main”的事件循环中并稍后调度(这种可能性更大)。
与顺序方法相比,此过程需要非常非常长的时间。请记住,您还需要将处理后的数据发回,并且filter
和filterWorker
的事件侦听器可能会在比您最初预期的时间更晚的时间触发。但即使你真的处理数据并显示它,下面的调用draw
也会毁掉你所有的辛勤工作。
解决方案
要么不使用网络工作者,要么使用隐藏的画布作为工作者的来源。