javascript
Web Worker javascript多线程编程(一)
什么是Web Worker?
web worker 是運(yùn)行在后臺(tái)的 JavaScript,不占用瀏覽器自身線程,獨(dú)立于其他腳本,可以提高應(yīng)用的總體性能,并且提升用戶體驗(yàn)。
一般來說Javascript和UI頁面會(huì)共用一個(gè)線程,在HTML頁面中執(zhí)行js腳本時(shí),頁面的狀態(tài)是不可響應(yīng)的,直到腳本已完成。而這段代碼可以交給Web Worker在后臺(tái)運(yùn)行,那么頁面在Javascript運(yùn)行期間依然可以響應(yīng)用戶操作。后臺(tái)會(huì)啟動(dòng)一個(gè)worker線程來執(zhí)行這段代碼,用戶可以創(chuàng)建多個(gè)worker線程。
有兩種 Web Worker
Web?workers可分為兩種類型:專用線程dedicated web worker,以及共享線程shared web worker。 Dedicated web worker隨當(dāng)前頁面的關(guān)閉而結(jié)束;這意味著Dedicated web worker只能被創(chuàng)建它的頁面訪問。與之相對(duì)應(yīng)的Shared web worker可以被多個(gè)頁面訪問。在Javascript代碼中,“Work”類型代表Dedicated web worker,而“SharedWorker”類型代表Shared web worker。
在絕大多數(shù)情況下,使用Dedicated web worker就足夠了,因?yàn)橐话銇碚f在web worker中運(yùn)行的代碼是專為當(dāng)前頁面服務(wù)的。而在一些特定情況下,web?worker可能運(yùn)行的是更為普遍性的代碼,可以為多個(gè)頁面服務(wù)。在這種情況下,我們會(huì)創(chuàng)建一個(gè)共享線程的Shared web worker,它可以被與之相關(guān)聯(lián)的多個(gè)頁面訪問,只有當(dāng)所有關(guān)聯(lián)的的頁面都關(guān)閉的時(shí)候,該Shared web worker才會(huì)結(jié)束。相對(duì)Dedicated web worker,shared web worker稍微復(fù)雜些。
new Worker()對(duì)象代表Dedicated Web Worker,以下示例代碼都為Dedicated Web Worker。
如何創(chuàng)建 Web Worker?
創(chuàng)建一個(gè)新的 worker 十分簡單。你所要做的就是調(diào)用?Worker()?構(gòu)造函數(shù),指定一個(gè)要在 worker 線程內(nèi)運(yùn)行的腳本的 URI,如果你希望能夠與worker進(jìn)行通信,接收其傳遞回來的數(shù)據(jù),可以將worker的onmessage屬性設(shè)置成一個(gè)特定的事件處理函數(shù),當(dāng) web worker 傳遞消息時(shí),會(huì)執(zhí)行事件監(jiān)聽器中的代碼。event.data 中存有來自 worker 的數(shù)據(jù)。。
example.html: (主頁面):
var myWorker = new Worker("worker_demo.js");myWorker.onmessage = function (event) {console.log("Called back by the worker!\n"); };或者,也可以使用?addEventListener()添加事件監(jiān)聽器:
例子中的第一行創(chuàng)建了一個(gè)新的 worker 線程。第三行為 worker 設(shè)置了?message?事件的監(jiān)聽函數(shù)。當(dāng) worker 調(diào)用自己的?postMessage() 函數(shù)時(shí)就會(huì)向后臺(tái)Worker發(fā)送數(shù)據(jù),并且后臺(tái)返回消息調(diào)用message這個(gè)事件處理函數(shù)。
注意: 傳入 Worker?構(gòu)造函數(shù)的參數(shù) URI 必須遵循同源策略為了高效地傳輸 ArrayBuffer 對(duì)象數(shù)據(jù),需要在 postMessage 方法中的第二個(gè)參數(shù)中指定它。實(shí)例代碼如下:
myWorker.postMessage({ operation: 'list_all_users', //ArrayBuffer object input: buffer, threshold: 0.8, }, [buffer]);worker_demo.js (worker):
注意:?通常來說,后臺(tái)線程 – 包括 worker – 無法操作 DOM。?如果后臺(tái)線程需要修改 DOM,那么它應(yīng)該將消息發(fā)送給它的創(chuàng)建者,讓創(chuàng)建者來完成這些操作。
通過Web Worker你可以在前臺(tái)做一些小規(guī)模分布式計(jì)算之類的工作,不過Web?Worker有以下一些使用限制:
- Web?Worker無法訪問DOM節(jié)點(diǎn);
- Web?Worker無法訪問全局變量或是全局函數(shù);
- Web?Worker無法訪問window、document之類的瀏覽器全局變量、方法;
不過Web?Worker作用域中依然可以使用有:
- 定時(shí)器相關(guān)方法 setTimeout(),clearTimeout(),setInterval()...之類的函數(shù)
- navigator對(duì)象,它含有如下能夠識(shí)別瀏覽器的字符串,就像在普通腳本中做的那樣,如:appName、appVersion、userAgent...
- 引入腳本與庫,Worker 線程能夠訪問一個(gè)全局函數(shù),importScripts()?,該函數(shù)允許 worker 將腳本或庫引入自己的作用域內(nèi)。你可以不傳入?yún)?shù),或傳入多個(gè)腳本的 URI 來引入;以下的例子都是合法的:
importScripts(); /* 什么都不引入 */ importScripts('foo.js'); /* 只引入 "foo.js" */ importScripts('foo.js', 'bar.js'); /* 引入兩個(gè)腳本 */ 瀏覽器將列出的腳本加載并運(yùn)行。每個(gè)腳本中的全局對(duì)象都能夠被 worker 使用。如果腳本無法加載,將拋出?NETWORK_ERROR?異常,接下來的代碼也無法執(zhí)行。而之前執(zhí)行的代碼(包括使用setTimeout延遲執(zhí)行的代碼)卻依然能夠使用。importScripts()之后的函數(shù)聲明依然能夠使用,因?yàn)樗鼈兪冀K會(huì)在其他代碼之前運(yùn)行。
注意:?腳本的下載順序不固定,但執(zhí)行時(shí)會(huì)按照你將文件名傳入到importScripts()中的順序。這是同步完成的;直到所有腳本都下載并運(yùn)行完畢,importScripts()才會(huì)返回。 - atob() 、btoa()? base64編碼與解碼的方法。
- 也可以使用XMLHttpRequest對(duì)象來做Ajax通信,以及其他API:WebSocket、Promise、Worker(可以在Worker中使用Worker)
下面簡單寫下Web Worker使用XMLHttpRequest與服務(wù)端通信:
addEventListener("message", function(evt){var xhr = new XMLHttpRequest();xhr.open("GET", "serviceUrl"); //serviceUrl為后端j返回son數(shù)據(jù)的接口xhr.onload = function(){postMessage(xhr.responseText);};xhr.send(); },false);上述舉例的代碼有些簡陋,只是為了拋磚引玉,見諒。其他API與Web Worker的融合使用也是大同小異,大家可以自己琢磨琢磨。
終止 web worker
如果你想立即終止一個(gè)運(yùn)行中的 worker,可以調(diào)用 worker 的terminate()方法。被終止的Worker對(duì)象不能被重啟或重用,我們只能新建另一個(gè)Worker實(shí)例來執(zhí)行新的任務(wù)。
myWorker.terminate();?
處理錯(cuò)誤
當(dāng) worker 出現(xiàn)運(yùn)行時(shí)錯(cuò)誤時(shí),它的onerror事件處理函數(shù)會(huì)被調(diào)用。它會(huì)收到一個(gè)實(shí)現(xiàn)了ErrorEvent接口名為error的事件,供開發(fā)者捕捉錯(cuò)誤信息。下面的代碼展示了如何綁定error事件:
worker.addEventListener("error", function(evt){ alert("Line #" + evt.lineno + " - " + evt.message + " in " + evt.filename); }, false);如上可見,?Worker對(duì)象可以綁定error事件;而且evt對(duì)象中包含錯(cuò)誤所在的代碼文件(evt.filename)、錯(cuò)誤所在的代碼行數(shù)(evt.lineno)、以及錯(cuò)誤信息(evt.message)。
?
下面上一個(gè)完整的dedicated web worker 使用案例。
demo_worker.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>dedicated web worker</title> </head> <body> <p>Count numbers:<output id="result"></output> </p> <button id="startWorker">startWorker</button> <button id="endWorker">stopWorker</button> </body> <script>(function () {var result = document.querySelector('#result'),startWorker = document.querySelector('#startWorker'),endWorker = document.querySelector('#endWorker'),worker,data = 10;startWorker.addEventListener('click', function (event) {if (typeof Worker !== 'undefined') {if (typeof worker == "undefined") {worker = new Worker('./demo_workers.js');}worker.addEventListener('message', function (event) { result.innerHTML = event.data;}, false);worker.addEventListener("error", function (event) {alert("Line #" + event.lineno + " - " + event.message + " in " + event.filename);}, false);worker.postMessage(data);endWorker.addEventListener('click', function () {worker.terminate();}, false);} else {result.innerHTML = 'sry, your browser does not support Web workers...';}}, false);})(); </script> </html>這個(gè)HTML頁面中有個(gè)startWorker按鈕,點(diǎn)擊后會(huì)運(yùn)行一個(gè)Javascript文件。上面的代碼中首先檢測當(dāng)前瀏覽器是否支持Web Worker,不支持的話就顯示提醒信息。
按鈕的點(diǎn)擊事件中創(chuàng)建了Worker對(duì)象,并給它指定了Javascript腳本文件——demo_workers.js(稍后會(huì)有代碼),并且給Worker對(duì)象綁定了一個(gè)“message”事件。該事件會(huì)在后臺(tái)代碼(demo_workers.js)向頁面返回?cái)?shù)據(jù)時(shí)觸發(fā)。“message”事件可以通過event.data來獲取后臺(tái)代碼傳回的數(shù)據(jù)。最后,postMessage方法正式執(zhí)行demo_workers.js,該方法向后臺(tái)代碼傳遞參數(shù),后臺(tái)代碼同樣通過message事件參數(shù)的data屬性獲取。
demo_worker.js
addEventListener('message',function (event) {var count = event.data;var interval = setInterval(function () {postMessage(count--);!count && clearInterval(interval);},1000);});以上代碼在后臺(tái)監(jiān)聽message事件,并獲取頁面?zhèn)鱽淼膮?shù);這里實(shí)際上是一個(gè)從10到1的倒計(jì)時(shí):在message事件被觸發(fā)之后,把結(jié)果傳給頁面顯示出來。
所以當(dāng)點(diǎn)擊startWorker按鈕,頁面會(huì)在count number: 顯示從10遞減一變?yōu)樽罱K的1,在這10秒鐘內(nèi)頁面依然可以響應(yīng)鼠標(biāo)鍵盤事件。點(diǎn)擊stopWorker按鈕,web worker 會(huì)直接終止,頁面變化顯示會(huì)直接停止。
?
嵌入式web worker
目前沒有一種官方的方法能夠像script標(biāo)簽一樣將 worker 的代碼嵌入的網(wǎng)頁中。但是如果一個(gè)script元素沒有指定src屬性,并且它的type沒有指定成一個(gè)可運(yùn)行的 mime-type,那么它就會(huì)被認(rèn)為是一個(gè)數(shù)據(jù)塊元素,并且能夠被 JavaScript 使用。數(shù)據(jù)塊是 HTML5 中一個(gè)十分常見的特性,它可以攜帶幾乎任何文本類型的數(shù)據(jù)。所以,你能夠以如下方式嵌入一個(gè) worker:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>MDN Example - Embedded worker</title> <script type="text/js-worker">// 該腳本不會(huì)被 JS 引擎解析,因?yàn)樗?mime-type 是 text/js-worker。var myVar = "Hello World!";// 剩下的 worker 代碼寫到這里。 </script> <script type="text/javascript">// 該腳本會(huì)被 JS 引擎解析,因?yàn)樗?mime-type 是 text/javascript。function pageLog (sMsg) {// 使用 fragment:這樣瀏覽器只會(huì)進(jìn)行一次渲染/重排。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">// 該腳本不會(huì)被 JS 引擎解析,因?yàn)樗?mime-type 是 text/js-worker。 onmessage = function (oEvent) {postMessage(myVar);};// 剩下的 worker 代碼寫到這里。 </script> <script type="text/javascript">// 該腳本會(huì)被 JS 引擎解析,因?yàn)樗?mime-type 是 text/javascript。// 在過去...:// 我們使用 blob builder// ...但是現(xiàn)在我們使用 Blob...:var blob = new Blob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function (oScript) { return oScript.textContent; }),{type: "text/javascript"});// 創(chuàng)建一個(gè)新的 document.worker 屬性,包含所有 "text/js-worker" 腳本。 document.worker = new Worker(window.URL.createObjectURL(blob));document.worker.onmessage = function (oEvent) {pageLog("Received: " + oEvent.data);};// 啟動(dòng) worker. window.onload = function() { document.worker.postMessage(""); }; </script> </head> <body><div id="logDisplay"></div></body> </html>現(xiàn)在,嵌入式 worker 已經(jīng)嵌套進(jìn)了一個(gè)自定義的?document.worker?屬性中。
?
在 worker 內(nèi)創(chuàng)建 worker
worker 的一個(gè)優(yōu)勢在于能夠執(zhí)行處理器密集型的運(yùn)算而不會(huì)阻塞 UI 線程。在下面的例子中,worker 用于計(jì)算斐波那契數(shù)。
fibonacci.js
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設(shè)置為一個(gè)函數(shù),當(dāng)worker對(duì)象調(diào)用?postMessage()時(shí)該函數(shù)會(huì)接收到發(fā)送過來的信息。(注意,這么使用并不等同于定義一個(gè)同名的全局變量,或是定義一個(gè)同名的函數(shù)。var onmessage 與 function onmessage 將會(huì)定義與該名字相同的全局屬性,但是它們不會(huì)注冊(cè)能夠接收從創(chuàng)建 worker 的網(wǎng)頁發(fā)送過來的消息的函數(shù)。) 這會(huì)啟用遞歸,生成自己的新拷貝來處理計(jì)算的每一個(gè)循環(huán)。
fibonacci.html
<!DOCTYPE html> <html><head><meta charset="UTF-8" /><title>Test threads fibonacci</title></head><body><div id="result"></div><script>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>網(wǎng)頁創(chuàng)建了一個(gè)div元素,ID為result,用它來顯示運(yùn)算結(jié)果,然后生成worker。在生成worker后,onmessage處理函數(shù)配置為通過設(shè)置div元素的內(nèi)容來顯示運(yùn)算結(jié)果,最后,向worker發(fā)送一條信息來啟動(dòng)它。
注意:chrome下不支持在worker中創(chuàng)建worker、以及dump方法、所以上述代碼可以在Firefox下運(yùn)行。由于文章篇幅過長,關(guān)于共享線程shared web worker的介紹將在下篇文章Web Worker javascript多線程編程(二)發(fā)布。
?
轉(zhuǎn)載于:https://www.cnblogs.com/peakleo/p/6218823.html
總結(jié)
以上是生活随笔為你收集整理的Web Worker javascript多线程编程(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。