HTML5之web workers_动力节点Java学院整理

HTML5之web workers_动力节点Java学院整理,第1张

HTML5之web workers_动力节点Java学院整理

专用 Web Worker (Dedicated Web Worker) 提供了一个简单的方法使得 web 内容能够在后台运行脚本。


一旦 worker 创建后,它可以向由它的创建者指定的事件监听函数传递消息,这样该 worker 生成的所有任务就都会接收到这些消息。


worker 线程能够在不干扰 UI 的情况下执行任务。


另外,它还能够使用XMLHttpRequest(虽然responseXML与channel 两个属性值始终是null)来执行I/O *** 作。


本文通过提供例子和细节补全了前面的文档。


提供给 worker 的函数列出了 worker 所支持的函数。


Worker接口会生成真正的 *** 作系统级别的线程,如果你不太小心,那么并发(concurrency)会对你的代码产生有趣的影响。


然而,对于 web worker 来说,与其他线程的通信点会被很小心的控制,这意味着你很难引起并发问题。


你没有办法去访问非线程安全的组件或者是 DOM,此外你还需要通过序列化对象来与线程交互特定的数据。


所以你要是不费点劲儿,还真搞不出错误来。


生成 worker

创建一个新的 worker 十分简单。


你所要做的就是调用Worker()构造函数,指定一个要在 worker 线程内运行的脚本的 URI,如果你希望能够收到 worker 的通知,可以将 worker 的onmessage属性设置成一个特定的事件处理函数。


var myWorker = new Worker("my_task.js");
myWorker.onmessage = function (oEvent) {
  console.log("Called back by the worker!\n");
};

或者,你也可以使用addEventListener():

var myWorker = new Worker("my_task.js");
myWorker.addEventListener("message", function (oEvent) {
  console.log("Called back by the worker!\n");
}, false);
myWorker.postMessage(""); // start the worker.

例子中的第一行创建了一个新的 worker 线程。


第三行为 worker 设置了message事件的监听函数。


当 worker 调用自己的postMessage() 函数时就会调用这个事件处理函数。


最后,第七行启动了 worker 线程。


注意: 传入Worker构造函数的参数 URI 必须遵循同源策略。


目前,不同的浏览器制造商对于哪些 URI 应该遵循同源策略尚有分歧;Gecko 10.0 (Firefox 10.0 / Thunderbird 10.0 / SeaMonkey 2.7) 及后续版本允许传入 data URI,而 Internet Explorer 10 则不认为 Blob URI 对于 worker 来说是一个有效的脚本。


传递数据

在主页面与 worker 之间传递的数据是通过拷贝,而不是共享来完成的。


传递给worker 的对象需要经过序列化,接下来在另一端还需要反序列化。


页面与 worker不会共享同一个实例,最终的结果就是在每次通信结束时生成了数据的一个副本。


大部分浏览器使用结构化拷贝来实现该特性。


在往下进行之前,出于教学的目的,让我们创建一个名为emulateMessage()的函数,它将模拟在从worker到主页面(反之亦然)的通信过程中,变量的「拷贝而非共享」行为:

function emulateMessage (vVal) {
    return eval("(" + JSON.stringify(vVal) + ")");
}
// Tests
// test #1
var example1 = new Number(3);
alert(typeof example1); // object
alert(typeof emulateMessage(example1)); // number
// test #2
var example2 = true;
alert(typeof example2); // boolean
alert(typeof emulateMessage(example2)); // boolean
// test #3
var example3 = new String("Hello World");
alert(typeof example3); // object
alert(typeof emulateMessage(example3)); // string
// test #4
var example4 = {
    "name": "John Smith",
    "age": 43
};
alert(typeof example4); // object
alert(typeof emulateMessage(example4)); // object
// test #5
function Animal (sType, nAge) {
    this.type = sType;
    this.age = nAge;
}
var example5 = new Animal("Cat", 3);
alert(example5.constructor); // Animal
alert(emulateMessage(example5).constructor); // Object

拷贝而并非共享的那个值称为消息。


再来谈谈worker,你可以使用postMessage() 将消息传递给主线程或从主线程传送回来。


message事件的data属性就包含了从 worker 传回来的数据。


example.html: (主页面):

var myWorker = new Worker("my_task.js");
myWorker.onmessage = function (oEvent) {
  console.log("Worker said : " + oEvent.data);
};
myWorker.postMessage("ali");
my_task.js (worker):
postMessage("I\'m working before postMessage(\'ali\').");
onmessage = function (oEvent) {
  postMessage("Hi " + oEvent.data);
};

注意:通常来说,后台线程 – 包括 worker –无法 *** 作 DOM。


如果后台线程需要修改 DOM,那么它应该将消息发送给它的创建者,让创建者来完成这些 *** 作。


如你所见,worker与主页面之间传输的消息始终是「JSON 消息」,即使它是一个原始类型的值。


所以,你完全可以传输JSON数据 和/或 任何能够序列化的数据类型:

postMessage({"cmd": "init", "timestamp": Date.now()});

传递数据的例子

例子 #1: 创建一个通用的 「异步eval()」

下面这个例子介绍了,如何在 worker 内使用eval()来按顺序执行异步的任何种类的 JavaScript 代码:

// Syntax: asyncEval(code[, listener])
var asyncEval = (function () {
  var aListeners = [], oParser = new Worker("data:text/javascript;charset=US-ASCII,onmessage%20%3D%20function%20%28oEvent%29%20%7B%0A%09postMessage%28%7B%0A%09%09%22id%22%3A%20oEvent.data.id%2C%0A%09%09%22evaluated%22%3A%20eval%28oEvent.data.code%29%0A%09%7D%29%3B%0A%7D");
  oParser.onmessage = function (oEvent) {
    if (aListeners[oEvent.data.id]) { aListeners[oEvent.data.id](oEvent.data.evaluated); }
    delete aListeners[oEvent.data.id];
  };
  return function (sCode, fListener) {
    aListeners.push(fListener || null);
    oParser.postMessage({
      "id": aListeners.length - 1,
      "code": sCode
    });
  };
})();

示例使用:

// asynchronous alert message...
asyncEval("3 + 2", function (sMessage) {
    alert("3 + 2 = " + sMessage);
});
// asynchronous print message...
asyncEval("\"Hello World!!!\"", function (sHTML) {
    document.body.appendChild(document.createTextNode(sHTML));
});
// asynchronous void...
asyncEval("(function () {\n\tvar oReq = new XMLHttpRequest();\n\toReq.open(\"get\", \"http://www.mozilla.org/\", false);\n\toReq.send(null);\n\treturn oReq.responseText;\n})()");

例子 #2:传输 JSON 的高级方式和创建一个交换系统

如果你需要传输非常复杂的数据,还要同时在主页与 Worker 内调用多个方法,那么可以考虑创建一个类似下面的系统。


example.html(the main page):

<!doctype html>
<html>
<head>
<meta charset="UTF-8"  />
<title>MDN Example - Queryable worker</title>
<script type="text/javascript">
  /*
    QueryableWorker instances methods:
     * sendQuery(queryable function name, argument to pass 1, argument to pass 2, etc. etc): calls a Worker's queryable function
     * postMessage(string or JSON Data): see Worker.prototype.postMessage()
     * terminate(): terminates the Worker
     * addListener(name, function): adds a listener
     * removeListener(name): removes a listener
    QueryableWorker instances properties:
     * defaultListener: the default listener executed only when the Worker calls the postMessage() function directly
  */
  function QueryableWorker (sURL, fDefListener, fOnError) {
    var oInstance = this, oWorker = new Worker(sURL), oListeners = {};
    this.defaultListener = fDefListener || function () {};
    oWorker.onmessage = function (oEvent) {
      if (oEvent.data instanceof Object && oEvent.data.hasOwnProperty("vo42t30") && oEvent.data.hasOwnProperty("rnb93qh")) {
        oListeners[oEvent.data.vo42t30].apply(oInstance, oEvent.data.rnb93qh);
      } else {
        this.defaultListener.call(oInstance, oEvent.data);
      }
    };
    if (fOnError) { oWorker.onerror = fOnError; }
    this.sendQuery = function (/* queryable function name, argument to pass 1, argument to pass 2, etc. etc */) {
      if (arguments.length < 1) { throw new TypeError("QueryableWorker.sendQuery - not enough arguments"); return; }
      oWorker.postMessage({ "bk4e1h0": arguments[0], "ktp3fm1": Array.prototype.slice.call(arguments, 1) });
    };
    this.postMessage = function (vMsg) {
      //I just think there is no need to use call() method
      //how about just oWorker.postMessage(vMsg);
      //the same situation with terminate
      //well,just a little faster,no search up the prototye chain
      Worker.prototype.postMessage.call(oWorker, vMsg);
    };
    this.terminate = function () {
      Worker.prototype.terminate.call(oWorker);
    };
    this.addListener = function (sName, fListener) {
      oListeners[sName] = fListener;
    };
    this.removeListener = function (sName) {
      delete oListeners[sName];
    };
  };
  // your custom "queryable" worker
  var oMyTask = new QueryableWorker("my_task.js" /* , yourDefaultMessageListenerHere [optional], yourErrorListenerHere [optional] */);
  // your custom "listeners"
  oMyTask.addListener("printSomething", function (nResult) {
    document.getElementById("firstLink").parentNode.appendChild(document.createTextNode(" The difference is " + nResult + "!"));
  });
  oMyTask.addListener("alertSomething", function (nDeltaT, sUnit) {
    alert("Worker waited for " + nDeltaT + " " + sUnit + " :-)");
  });
</script>
</head>
<body>
  <ul>
    <li><a id="firstLink" href="javascript:oMyTask.sendQuery('getDifference', 5, 3);">What is the difference between 5 and 3?</a></li>
    <li><a href="javascript:oMyTask.sendQuery('waitSomething');">Wait 3 seconds</a></li>
    <li><a href="javascript:oMyTask.terminate();">terminate() the Worker</a></li>
  </ul>
</body>
</html>
my_task.js (the worker):
// your custom PRIVATE functions
function myPrivateFunc1 () {
  // do something
}
function myPrivateFunc2 () {
  // do something
}
// etc. etc.
// your custom PUBLIC functions (i.e. queryable from the main page)
var queryableFunctions = {
  // example #1: get the difference between two numbers:
  getDifference: function (nMinuend, nSubtrahend) {
      reply("printSomething", nMinuend - nSubtrahend);
  },
  // example #2: wait three seconds
  waitSomething: function () {
      setTimeout(function() { reply("alertSomething", 3, "seconds"); }, 3000);
  }
};
// system functions
function defaultQuery (vMsg) {
  // your default PUBLIC function executed only when main page calls the queryableWorker.postMessage() method directly
  // do something
}
function reply (/* listener name, argument to pass 1, argument to pass 2, etc. etc */) {
  if (arguments.length < 1) { throw new TypeError("reply - not enough arguments"); return; }
  postMessage({ "vo42t30": arguments[0], "rnb93qh": Array.prototype.slice.call(arguments, 1) });
}
onmessage = function (oEvent) {
  if (oEvent.data instanceof Object && oEvent.data.hasOwnProperty("bk4e1h0") && oEvent.data.hasOwnProperty("ktp3fm1")) {
    queryableFunctions[oEvent.data.bk4e1h0].apply(self, oEvent.data.ktp3fm1);
  } else {
    defaultQuery(oEvent.data);
  }
};

这是一个非常合适的方法,用于切换 主页-worker - 或是相反的 - 之间的消息。


通过转让所有权(可转让对象)来传递数据

Google Chrome 17 与 Firefox 18 包含另一种性能更高的方法来将特定类型的对象(可转让对象) 传递给一个 worker/从 worker 传回 。


可转让对象从一个上下文转移到另一个上下文而不会经过任何拷贝 *** 作。


这意味着当传递大数据时会获得极大的性能提升。


如果你从 C/C++ 世界来,那么把它想象成按照引用传递。


然而与按照引用传递不同的是,一旦对象转让,那么它在原来上下文的那个版本将不复存在。


该对象的所有权被转让到新的上下文内。


例如,当你将一个ArrayBuffer对象从主应用转让到Worker 中,原始的ArrayBuffer被清除并且无法使用。


它包含的内容会(完整无差的)传递给 Worker 上下文。


// Create a 32MB "file" and fill it.
var uInt8Array = new Uint8Array(1024*1024*32); // 32MB
for (var i = 0; i < uInt8Array .length; ++i) {
  uInt8Array[i] = i;
}
worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);

生成subworker

如果需要的话 Worker 能够生成更多的 Worker。


这样的被称为 subworker,它们必须托管在与父页面相同的源内。


同理,subworker 解析 URI 时会相对于父 worker 的地址而不是自身的页面。


这使得 worker 容易监控它们的依赖关系。


Chrome 目前并不支持subworker。


嵌入式 worker

目前没有一种「官方」的方法能够像<script>元素一样将 worker 的代码嵌入的网页中。


但是如果一个<script>元素没有src 特性,并且它的type特性没有指定成一个可运行的 mime-type,那么它就会被认为是一个数据块元素,并且能够被 JavaScript 使用。


「数据块」是 HTML5 中一个十分常见的特性,它可以携带几乎任何文本类型的数据。


所以,你能够以如下方式嵌入一个 worker:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>MDN Example - Embedded worker</title>
<script type="text/js-worker">
  // 该脚本不会被 JS 引擎解析,因为它的 mime-type 是 text/js-worker。


var myVar = "Hello World!"; // 剩下的 worker 代码写到这里。


</script> <script type="text/javascript"> // 该脚本会被 JS 引擎解析,因为它的 mime-type 是 text/javascript。


function pageLog (sMsg) { // 使用 fragment:这样浏览器只会进行一次渲染/重排。


var oFragm = document.createDocumentFragment(); oFragm.appendChild(document.createTextNode(sMsg)); oFragm.appendChild(document.createElement("br")); document.querySelector("#logDisplay").appendChild(oFragm); } </script> <script type="text/js-worker"> // 该脚本不会被 JS 引擎解析,因为它的 mime-type 是 text/js-worker。


onmessage = function (oEvent) { postMessage(myVar); }; // 剩下的 worker 代码写到这里。


</script> <script type="text/javascript"> // 该脚本会被 JS 引擎解析,因为它的 mime-type 是 text/javascript。


// 在过去...: // 我们使用 blob builder // ...但是现在我们使用 Blob...: var blob = new Blob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function (oScript) { return oScript.textContent; }),{type: "text/javascript"}); // 创建一个新的 document.worker 属性,包含所有 "text/js-worker" 脚本。


document.worker = new Worker(window.URL.createObjectURL(blob)); document.worker.onmessage = function (oEvent) { pageLog("Received: " + oEvent.data); }; // 启动 worker. window.onload = function() { document.worker.postMessage(""); }; </script> </head> <body><div id="logDisplay"></div></body> </html>

现在,嵌入式 worker 已经嵌套进了一个自定义的document.worker属性中。


超时与间隔

Worker 能够像主线程一样使用超时与间隔。


这会十分有用,比如说,如果你想让 worker 线程周期性而并非不间断的运行代码。


终止 worker

如果你想立即终止一个运行中的 worker,可以调用 worker 的terminate()方法:

myWorker.terminate();

worker 线程会被立即杀死,不会留下任何机会让它完成自己的 *** 作或清理工作。


Workers 也可以调用自己的nsIWorkerScope.close()方法来关闭自己:

self.close();

处理错误

当 worker 出现运行时错误时,它的onerror事件处理函数会被调用。


它会收到一个实现了ErrorEvent接口名为error的事件。


该事件不会冒泡,并且可以被取消;为了防止触发默认动作,worker 可以调用错误事件的preventDefault()方法。


错误事件拥有下列三个它感兴趣的字段:

message

可读性良好的错误消息。


filename

发生错误的脚本文件名。


lineno

发生错误时所在脚本文件的行号。


访问 navigator 对象

Workers 可以在它的作用域内访问navigator对象。


它含有如下能够识别浏览器的字符串,就像在普通脚本中做的那样:

  • appName
  • appVersion
  • platform
  • userAgent

引入脚本与库

Worker 线程能够访问一个全局函数,importScripts(),该函数允许 worker 将脚本或库引入自己的作用域内。


你可以不传入参数,或传入多个脚本的 URI 来引入;以下的例子都是合法的:

importScripts();                        /* 什么都不引入 */
importScripts('foo.js');                /* 只引入 "foo.js" */
importScripts('foo.js', 'bar.js');      /* 引入两个脚本 */

浏览器将列出的脚本加载并运行。


每个脚本中的全局对象都能够被 worker 使用。


如果脚本无法加载,将抛出NETWORK_ERROR异常,接下来的代码也无法执行。


而之前执行的代码(包括使用window.setTimeout()延迟执行的代码)却依然能够使用。


importScripts()之后的函数声明依然能够使用,因为它们始终会在其他代码之前运行。


注意:脚本的下载顺序不固定,但执行时会按照你将文件名传入到importScripts()中的顺序。


这是同步完成的;直到所有脚本都下载并运行完毕,importScripts()才会返回。


例子

本节提供了几个如何使用 DOM worker 的例子。


在后台执行运算

worker 的一个优势在于能够执行处理器密集型的运算而不会阻塞 UI 线程。


在下面的例子中,worker 用于计算斐波那契数。


JavaScript 代码

下面的 JavaScript 代码保存在「fibonacci.js」文件中,与下一节的 HTML 文件关联。


var results = [];
function resultReceiver(event) {
  results.push(parseInt(event.data));
  if (results.length == 2) {
    postMessage(results[0] + results[1]);
  }
}
function errorReceiver(event) {
  throw event.data;
}
onmessage = function(event) {
  var n = parseInt(event.data);
  if (n == 0 || n == 1) {
    postMessage(n);
    return;
  }
  for (var i = 1; i <= 2; i++) {
    var worker = new Worker("fibonacci.js");
    worker.onmessage = resultReceiver;
    worker.onerror = errorReceiver;
    worker.postMessage(n - i);
  }
 };

worker 将属性onmessage设置为一个函数,当 worker 对象调用postMessage() 时该函数会接收到发送过来的信息。


(注意,这么使用并不等同于定义一个同名的全局变量,或是定义一个同名的函数。


var onmessage与function onmessage将会定义与该名字相同的全局属性,但是它们不会注册能够接收从创建 worker 的网页发送过来的消息的函数。


) 这会启用递归,生成自己的新拷贝来处理计算的每一个循环。


HTML 代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"  />
    <title>Test threads fibonacci</title>
  </head>
  <body>
  <div id="result"></div>
  <script language="javascript">
    var worker = new Worker("fibonacci.js");
    worker.onmessage = function(event) {
      document.getElementById("result").textContent = event.data;
      dump("Got: " + event.data + "\n");
    };
    worker.onerror = function(error) {
      dump("Worker error: " + error.message + "\n");
      throw error;
    };
    worker.postMessage("5");
  </script>
  </body>
</html>

网页创建了一个div元素,ID 为result, 用它来显示运算结果,然后生成 worker。


在生成 worker 后,onmessage处理函数配置为通过设置div元素的内容来显示运算结果,然后onerror处理函数被设置为转储错误信息。


最后,向 worker 发送一条信息来启动它。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/615064.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-14
下一篇 2022-04-14

发表评论

登录后才能评论

评论列表(0条)

保存