如何让Webworker真正响应,以及为什么setTimeout()无法正常工作

时间:2015-03-24 13:54:46

标签: javascript multithreading web-worker

我正在研究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);
    }
});

页面应该如何操作:当我点击开始按钮时,它会向工作人员发送一条消息,该消息是调用运行函数。在这个函数中有一个while循环。在那里,首先向UI线程发送一条消息,其中当前时间显示在<TEXTAREA>中。接下来,调用setTimeout()stepTime <SELECT>元素设置的时间内暂停。此外,当循环开始时,必须禁用 Start 按钮并启用 Stop 。单击 Stop 时,它会将Worker中的running标志更改为false并停止循环。此外,必须启用开始按钮并禁用停止。总是当我单击按钮时,控制台会显示发送给worker的消息,然后显示来自Worker的UI线程接收的消息。当我更改stepTime <SELECT>值时,工作人员暂停时间会发生变化。 Clean 按钮只是清除<TEXTAREA>

我真正得到的:当我点击开始时,<TEXTAREA>充满了当前时间消息,控件根本没有响应。实际上所有的浏览器实例(我在Firefox中测试过)都会冻结并在一段时间后崩溃。显然,setTimeout()在Worker中不起作用,但在UI中起作用(例如,在控制台中我键入setTimeout(function() {console.debug("PAUSE!!!")}, id("stepTime").value);)。

我尝试过的解决方法是用setTimeout()函数替换pause()调用。我明白了:页面(和浏览器)没有冻结。 <TEXTAREA>显示时间消息,最初在<SELECT>中设置的时间间隔。 Clean 按钮按预期工作。但是,当循环启动时,它会在worker(console.debug("[WORKER] STARTING. running = " + running);)中显示控制台消息,但不显示工作者响应(postMessage({msg: "STARTED"});)。 Start 未启用, stop 未启用。此外,如果我更改<SELECT>值,则不会更改间隔。如果我真的想要更改间隔,我需要重新加载页面并设置另一个值。

查看代码很明显为什么在启动循环时没有调用响应,因为run()函数是一个无限循环,因此永远不会达到响应命令。

因此,我有两个问题:

  1. 如何更改代码以使其真正同步和 响应。
  2. 如果setTimeout()符合Web Worker definition
  3. 的原因,{{1}}无效

    谢谢,

    Rafael Afonso

2 个答案:

答案 0 :(得分:3)

1)对于第一个问题: 这段代码:

function run() {
    while(running) {
        postMessage({msg: "TIME", time: new Date().toISOString()});
        // pause();
        setTimeout(function() {console.debug("PAUSE!!!")}, stepTime);
    }
}

会挂起您的浏览器,因为它会{c}运行速度postMessage,因为您在某个while(true)里面会冻结您的网络工作者。 pause函数也会这样做,基本上它是一段时间(true)。 要使此代码以异步方式运行,您应该控制何时在您的网络工程师中调用postMessage,例如每10%的工作完成...不会在一段时间内完成。

2)对于第二个问题: 这行代码在while

setTimeout(function() {console.debug("PAUSE!!!")}, stepTime);

不会暂停它,它不会影响循环。在javascript中没有像php一样的sleep方法。

要获得所需的效果,请将run函数替换为:

function run() {
    if(running){
        postMessage({msg: "TIME", time: new Date().toISOString()});
        setTimeout(function() { run(); }, stepTime);
    }
}

答案 1 :(得分:1)

albanx:

根据您的建议,我更改了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>的步骤时间。最后,我可能需要多次重启循环。

我唯一的问题是:这些对runsetTimeout(run, stepTime);)的recrsive调用是否可以创建堆栈溢出?

谢谢,

Rafael Afonso