如何让 Webworker 真正响应以及为什么 setTimeout() 不起作用
How to get a Webworker really responsive and why setTimeout() not working
我正在研究Javascript Web Workers。为此,我创建了以下页面和脚本:
工人-test.html
<!DOCTYPE html>
<html>
<head>
<title>Web Worker Test</title>
<script src="worker-test.js"></script>
</head>
<body onload="init()">
<form>
<table width="50%">
<tr style="text-align: center;">
<td width="25%">
<label >Step Time</label>
<select id="stepTime" onchange="changeStepTime();">
<option>500</option>
<option>1000</option>
<option>2000</option>
</select>
</td>
<td width="25%">
<button id="btnStart" onclick="return buttonAction('START');">Start</start>
</td>
<td width="25%">
<button id="btnStop" onclick="return buttonAction('STOP');">Stop</start>
</td>
<td width="25%">
<button id="btnClean" onclick="return clean();">Clean</start>
</td>
</tr>
<tr>
<td colspan="4">
<textarea id="log" rows="15" style="width: 100%; font-size: xx-large;" readonly="readonly"></textarea>
</td>
</tr>
</table>
</form>
</body>
</html>
工人-test.js
function id(strId) {
return document.getElementById(strId);
}
worker = new Worker('worker.js');
worker.onmessage = function(e) {
switch(e.data.msg) {
case "STARTED":
console.info(e.data);
id("btnStart").disabled = true;
id("btnStop").disabled = false;
break;
case "STOPPED":
console.info(e.data);
id("btnStart").disabled = false;
id("btnStop").disabled = true;
break;
case "TIME":
id("log").value += e.data.time + '\n';
break
default:
// ...
}
};
function buttonAction(action) {
worker.postMessage({msg: action});
return false;
}
function changeStepTime() {
worker.postMessage({msg: "STEPTIME", stepTime: id("stepTime").value});
}
function clean() {
id('log').value = '';
return false;
}
function init() {
id("btnStop").disabled = true;
changeStepTime();
}
worker.js
var running = false;
var stepTime = 0;
// Replacement of setTimeout().
function pause() {
var t0 = Date.now();
while((Date.now() - t0) < stepTime) {
(function() {}) ();
}
}
function run() {
while(running) {
postMessage({msg: "TIME", time: new Date().toISOString()});
// pause();
setTimeout(function() {console.debug("PAUSE!!!")}, stepTime);
}
}
addEventListener('message', function(e) {
switch(e.data.msg) {
case "START":
running = true;
console.debug("[WORKER] STARTING. running = " + running);
run();
postMessage({msg: "STARTED"});
break;
case "STOP":
running = false;
console.debug("[WORKER] STOPPING. running = " + running);
postMessage({msg: "STOPPED"});
break;
case "STEPTIME":
stepTime = parseInt(e.data.stepTime);
console.debug("[WORKER] STEP TIME = " + stepTime);
postMessage({msg: "STEP TIME CHANGED"});
break;
default:
console.warn("[WORKER] " + e.data);
}
});
页面应该如何做:当我点击 Start 按钮时,它会向调用 运行 函数的工作人员发送一条消息。在这个函数中有一个 while 循环。在那里,首先向 UI 线程发送一条消息,其中包含要在 <TEXTAREA>
中显示的当前时间。接下来,调用 setTimeout()
以在 stepTime
<SELECT>
元素设置的时间暂停。此外,当循环开始时,必须禁用 Start 按钮并启用 Stop 按钮。单击 Stop 时,它会将 Worker 中的 running
标志更改为 false 并停止循环。此外,必须启用 Start 按钮并禁用 Stop 按钮。总是当我单击按钮时,控制台会显示发送给工作人员的消息,然后显示 UI 线程从工作人员收到的消息。当我更改 stepTime
<SELECT>
值时,工作人员暂停时间会发生变化。 清理 按钮只是清理 <TEXTAREA>
。
我真正得到的是:当我单击 开始 时,<TEXTAREA>
充满了当前时间消息,控件根本没有响应。实际上所有浏览器实例(我在 Firefox 中测试过)都冻结并在一段时间后崩溃。显然,setTimeout()
在 Worker 中不起作用,尽管它在 UI 中起作用(例如,在控制台中我键入 setTimeout(function() {console.debug("PAUSE!!!")}, id("stepTime").value);
)。
我尝试的解决方法是用 pause()
函数替换 setTimeout()
调用。我明白了:页面(和浏览器)没有冻结。 <TEXTAREA>
显示时间消息,间隔最初在 <SELECT>
中设置。 Clean 按钮按预期工作。但是,当循环开始时,它会在工作人员 (console.debug("[WORKER] STARTING. running = " + running);
) 中显示控制台消息,但不会显示工作人员响应 (postMessage({msg: "STARTED"});
)。 Start 未禁用,stop 未启用。此外,如果我更改 <SELECT>
值,它不会更改间隔。如果我真的想改变间隔,我需要重新加载页面并设置另一个值。
查看代码很明显为什么在我开始循环时没有调用响应,因为 run()
函数是一个无限循环,因此永远不会到达响应命令。
因此,我有两个问题:
- 如何更改代码使其真正异步和
反应灵敏。
- 如果 Web Worker definition 应该工作,为什么
setTimeout()
不工作。
谢谢,
拉斐尔·阿方索
1) 第一个问题:
此代码:
function run() {
while(running) {
postMessage({msg: "TIME", time: new Date().toISOString()});
// pause();
setTimeout(function() {console.debug("PAUSE!!!")}, stepTime);
}
}
会挂起你的浏览器,因为它 postMessage
和你的 cpu 运行 一样快,因为你在某种 while(true)
中,这会冻结你的网络工作者。 pause
函数也会做同样的事情,基本上是一段时间(真)。
要使此代码 运行 异步,您应该控制何时在您的 webworker 中调用 postMessage
,例如每 10% 的作业完成...而不是在一段时间内 true.
2)对于第二个问题:
while
里面的这行代码
setTimeout(function() {console.debug("PAUSE!!!")}, stepTime);
不会暂停,不影响循环。在 javascript 中没有像 php 那样的 sleep 方法。
要获得效果,您需要将 运行 函数替换为以下函数:
function run() {
if(running){
postMessage({msg: "TIME", time: new Date().toISOString()});
setTimeout(function() { run(); }, stepTime);
}
}
阿尔班克斯:
根据您的建议,我更改了 worker.js
代码,如下所示:
var running = false;
var stepTime = 0;
function run() {
if(running) {
postMessage({msg: "TIME", time: new Date().toISOString()});
setTimeout(run, stepTime);
}
}
self.addEventListener('message', function(e) {
switch(e.data.msg) {
case "START":
running = true;
console.debug("[WORKER] STARTING. running = " + running);
postMessage({msg: "STARTED"});
run();
break;
case "STOP":
running = false;
console.debug("[WORKER] STOPPING. running = " + running);
postMessage({msg: "STOPPED"});
break;
case "STEPTIME":
stepTime = parseInt(e.data.stepTime);
console.debug("[WORKER] STEP TIME = " + stepTime);
postMessage({msg: "STEP TIME CHANGED"});
break;
default:
console.warn("[WORKER] " + e.data);
}
});
这一次一切都按预期工作:停止 按钮已启用并真正停止了循环。也有可能改变步骤时间改变 <SELECT>
。最后,我可以多次重启循环。
我唯一的问题是:这些对 run
(setTimeout(run, stepTime);
) 的递归调用不会造成堆栈溢出吗?
谢谢,
拉斐尔·阿方索
我正在研究Javascript Web Workers。为此,我创建了以下页面和脚本:
工人-test.html
<!DOCTYPE html>
<html>
<head>
<title>Web Worker Test</title>
<script src="worker-test.js"></script>
</head>
<body onload="init()">
<form>
<table width="50%">
<tr style="text-align: center;">
<td width="25%">
<label >Step Time</label>
<select id="stepTime" onchange="changeStepTime();">
<option>500</option>
<option>1000</option>
<option>2000</option>
</select>
</td>
<td width="25%">
<button id="btnStart" onclick="return buttonAction('START');">Start</start>
</td>
<td width="25%">
<button id="btnStop" onclick="return buttonAction('STOP');">Stop</start>
</td>
<td width="25%">
<button id="btnClean" onclick="return clean();">Clean</start>
</td>
</tr>
<tr>
<td colspan="4">
<textarea id="log" rows="15" style="width: 100%; font-size: xx-large;" readonly="readonly"></textarea>
</td>
</tr>
</table>
</form>
</body>
</html>
工人-test.js
function id(strId) {
return document.getElementById(strId);
}
worker = new Worker('worker.js');
worker.onmessage = function(e) {
switch(e.data.msg) {
case "STARTED":
console.info(e.data);
id("btnStart").disabled = true;
id("btnStop").disabled = false;
break;
case "STOPPED":
console.info(e.data);
id("btnStart").disabled = false;
id("btnStop").disabled = true;
break;
case "TIME":
id("log").value += e.data.time + '\n';
break
default:
// ...
}
};
function buttonAction(action) {
worker.postMessage({msg: action});
return false;
}
function changeStepTime() {
worker.postMessage({msg: "STEPTIME", stepTime: id("stepTime").value});
}
function clean() {
id('log').value = '';
return false;
}
function init() {
id("btnStop").disabled = true;
changeStepTime();
}
worker.js
var running = false;
var stepTime = 0;
// Replacement of setTimeout().
function pause() {
var t0 = Date.now();
while((Date.now() - t0) < stepTime) {
(function() {}) ();
}
}
function run() {
while(running) {
postMessage({msg: "TIME", time: new Date().toISOString()});
// pause();
setTimeout(function() {console.debug("PAUSE!!!")}, stepTime);
}
}
addEventListener('message', function(e) {
switch(e.data.msg) {
case "START":
running = true;
console.debug("[WORKER] STARTING. running = " + running);
run();
postMessage({msg: "STARTED"});
break;
case "STOP":
running = false;
console.debug("[WORKER] STOPPING. running = " + running);
postMessage({msg: "STOPPED"});
break;
case "STEPTIME":
stepTime = parseInt(e.data.stepTime);
console.debug("[WORKER] STEP TIME = " + stepTime);
postMessage({msg: "STEP TIME CHANGED"});
break;
default:
console.warn("[WORKER] " + e.data);
}
});
页面应该如何做:当我点击 Start 按钮时,它会向调用 运行 函数的工作人员发送一条消息。在这个函数中有一个 while 循环。在那里,首先向 UI 线程发送一条消息,其中包含要在 <TEXTAREA>
中显示的当前时间。接下来,调用 setTimeout()
以在 stepTime
<SELECT>
元素设置的时间暂停。此外,当循环开始时,必须禁用 Start 按钮并启用 Stop 按钮。单击 Stop 时,它会将 Worker 中的 running
标志更改为 false 并停止循环。此外,必须启用 Start 按钮并禁用 Stop 按钮。总是当我单击按钮时,控制台会显示发送给工作人员的消息,然后显示 UI 线程从工作人员收到的消息。当我更改 stepTime
<SELECT>
值时,工作人员暂停时间会发生变化。 清理 按钮只是清理 <TEXTAREA>
。
我真正得到的是:当我单击 开始 时,<TEXTAREA>
充满了当前时间消息,控件根本没有响应。实际上所有浏览器实例(我在 Firefox 中测试过)都冻结并在一段时间后崩溃。显然,setTimeout()
在 Worker 中不起作用,尽管它在 UI 中起作用(例如,在控制台中我键入 setTimeout(function() {console.debug("PAUSE!!!")}, id("stepTime").value);
)。
我尝试的解决方法是用 pause()
函数替换 setTimeout()
调用。我明白了:页面(和浏览器)没有冻结。 <TEXTAREA>
显示时间消息,间隔最初在 <SELECT>
中设置。 Clean 按钮按预期工作。但是,当循环开始时,它会在工作人员 (console.debug("[WORKER] STARTING. running = " + running);
) 中显示控制台消息,但不会显示工作人员响应 (postMessage({msg: "STARTED"});
)。 Start 未禁用,stop 未启用。此外,如果我更改 <SELECT>
值,它不会更改间隔。如果我真的想改变间隔,我需要重新加载页面并设置另一个值。
查看代码很明显为什么在我开始循环时没有调用响应,因为 run()
函数是一个无限循环,因此永远不会到达响应命令。
因此,我有两个问题:
- 如何更改代码使其真正异步和 反应灵敏。
- 如果 Web Worker definition 应该工作,为什么
setTimeout()
不工作。
谢谢,
拉斐尔·阿方索
1) 第一个问题: 此代码:
function run() {
while(running) {
postMessage({msg: "TIME", time: new Date().toISOString()});
// pause();
setTimeout(function() {console.debug("PAUSE!!!")}, stepTime);
}
}
会挂起你的浏览器,因为它 postMessage
和你的 cpu 运行 一样快,因为你在某种 while(true)
中,这会冻结你的网络工作者。 pause
函数也会做同样的事情,基本上是一段时间(真)。
要使此代码 运行 异步,您应该控制何时在您的 webworker 中调用 postMessage
,例如每 10% 的作业完成...而不是在一段时间内 true.
2)对于第二个问题: while
里面的这行代码setTimeout(function() {console.debug("PAUSE!!!")}, stepTime);
不会暂停,不影响循环。在 javascript 中没有像 php 那样的 sleep 方法。
要获得效果,您需要将 运行 函数替换为以下函数:
function run() {
if(running){
postMessage({msg: "TIME", time: new Date().toISOString()});
setTimeout(function() { run(); }, stepTime);
}
}
阿尔班克斯:
根据您的建议,我更改了 worker.js
代码,如下所示:
var running = false;
var stepTime = 0;
function run() {
if(running) {
postMessage({msg: "TIME", time: new Date().toISOString()});
setTimeout(run, stepTime);
}
}
self.addEventListener('message', function(e) {
switch(e.data.msg) {
case "START":
running = true;
console.debug("[WORKER] STARTING. running = " + running);
postMessage({msg: "STARTED"});
run();
break;
case "STOP":
running = false;
console.debug("[WORKER] STOPPING. running = " + running);
postMessage({msg: "STOPPED"});
break;
case "STEPTIME":
stepTime = parseInt(e.data.stepTime);
console.debug("[WORKER] STEP TIME = " + stepTime);
postMessage({msg: "STEP TIME CHANGED"});
break;
default:
console.warn("[WORKER] " + e.data);
}
});
这一次一切都按预期工作:停止 按钮已启用并真正停止了循环。也有可能改变步骤时间改变 <SELECT>
。最后,我可以多次重启循环。
我唯一的问题是:这些对 run
(setTimeout(run, stepTime);
) 的递归调用不会造成堆栈溢出吗?
谢谢,
拉斐尔·阿方索