Web Worker javascript多線程編程(一)

什麼是Web Worker?javascript

web worker 是運行在後臺的 JavaScript,不佔用瀏覽器自身線程,獨立於其餘腳本,能夠提升應用的整體性能,而且提高用戶體驗。html

通常來講Javascript和UI頁面會共用一個線程,在HTML頁面中執行js腳本時,頁面的狀態是不可響應的,直到腳本已完成。而這段代碼能夠交給Web Worker在後臺運行,那麼頁面在Javascript運行期間依然能夠響應用戶操做。後臺會啓動一個worker線程來執行這段代碼,用戶能夠建立多個worker線程。java

有兩種 Web Worker

Web workers可分爲兩種類型:專用線程dedicated web worker,以及共享線程shared web worker。 Dedicated web worker隨當前頁面的關閉而結束;這意味着Dedicated web worker只能被建立它的頁面訪問。與之相對應的Shared web worker能夠被多個頁面訪問。在Javascript代碼中,「Work」類型表明Dedicated web worker,而「SharedWorker」類型表明Shared web worker。web

在絕大多數狀況下,使用Dedicated web worker就足夠了,由於通常來講在web worker中運行的代碼是專爲當前頁面服務的。而在一些特定狀況下,web worker可能運行的是更爲廣泛性的代碼,能夠爲多個頁面服務。在這種狀況下,咱們會建立一個共享線程的Shared web worker,它能夠被與之相關聯的多個頁面訪問,只有當全部關聯的的頁面都關閉的時候,該Shared web worker纔會結束。相對Dedicated web worker,shared web worker稍微複雜些。chrome

new Worker()對象表明Dedicated Web Worker,如下示例代碼都爲Dedicated Web Worker。
編程

如何建立 Web Worker?後端

建立一個新的 worker 十分簡單。你所要作的就是調用 Worker() 構造函數,指定一個要在 worker 線程內運行的腳本的 URI,若是你但願可以與worker進行通訊,接收其傳遞回來的數據,能夠將worker的onmessage屬性設置成一個特定的事件處理函數,當 web worker 傳遞消息時,會執行事件監聽器中的代碼。event.data 中存有來自 worker 的數據。。瀏覽器

example.html: (主頁面):多線程

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

或者,也可使用 addEventListener()添加事件監聽器:
app

var myWorker = new Worker("worker_demo.js"); myWorker.addEventListener("message", function (event) { console.log("Worker said : " + event.data); }, false); myWorker.postMessage("hello my worker"); // start the worker.

例子中的第一行建立了一個新的 worker 線程。第三行爲 worker 設置了 message 事件的監聽函數。當 worker 調用本身的 postMessage() 函數時就會向後臺Worker發送數據,而且後臺返回消息調用message這個事件處理函數。

注意: 傳入 Worker 構造函數的參數 URI 必須遵循同源策略爲了高效地傳輸 ArrayBuffer 對象數據,須要在 postMessage 方法中的第二個參數中指定它。實例代碼以下:

 myWorker.postMessage({ operation: 'list_all_users', //ArrayBuffer object 
 input: buffer, threshold: 0.8, }, [buffer]);

worker_demo.js (worker):

postMessage("I\'m working before postMessage(\'hello my worker\')."); onmessage = function (event) { postMessage("Hi " + event.data); };

注意: 一般來講,後臺線程 – 包括 worker – 沒法操做 DOM。 若是後臺線程須要修改 DOM,那麼它應該將消息發送給它的建立者,讓建立者來完成這些操做。

經過Web Worker你能夠在前臺作一些小規模分佈式計算之類的工做,不過Web Worker有如下一些使用限制:

  • Web Worker沒法訪問DOM節點;
  • Web Worker沒法訪問全局變量或是全局函數;
  • Web Worker沒法訪問window、document之類的瀏覽器全局變量、方法;

不過Web Worker做用域中依然可使用有:

  • 定時器相關方法 setTimeout(),clearTimeout(),setInterval()...之類的函數
  • navigator對象,它含有以下可以識別瀏覽器的字符串,就像在普通腳本中作的那樣,如:appName、appVersion、userAgent...
  • 引入腳本與庫,Worker 線程可以訪問一個全局函數,importScripts() ,該函數容許 worker 將腳本或庫引入本身的做用域內。你能夠不傳入參數,或傳入多個腳本的 URI 來引入;如下的例子都是合法的:
    importScripts();                        /* 什麼都不引入 */ importScripts('foo.js');                /* 只引入 "foo.js" */ importScripts('foo.js', 'bar.js');      /* 引入兩個腳本 */
    瀏覽器將列出的腳本加載並運行。每一個腳本中的全局對象都可以被 worker 使用。若是腳本沒法加載,將拋出 NETWORK_ERROR 異常,接下來的代碼也沒法執行。而以前執行的代碼(包括使用setTimeout延遲執行的代碼)卻依然可以使用。importScripts()以後的函數聲明依然可以使用,由於它們始終會在其餘代碼以前運行。
    注意: 腳本的下載順序不固定,但執行時會按照你將文件名傳入到importScripts()中的順序。這是同步完成的;直到全部腳本都下載並運行完畢,importScripts()纔會返回。
  • atob() 、btoa()  base64編碼與解碼的方法。
  • 也可使用XMLHttpRequest對象來作Ajax通訊,以及其餘API:WebSocket、Promise、Worker(能夠在Worker中使用Worker)
    下面簡單寫下
    Web Worker使用XMLHttpRequest與服務端通訊:
    addEventListener("message", function(evt){ var xhr = new XMLHttpRequest(); xhr.open("GET", "serviceUrl"); //serviceUrl爲後端j返回son數據的接口
        xhr.onload = function(){ postMessage(xhr.responseText); }; xhr.send(); },false);

    上述舉例的代碼有些簡陋,只是爲了拋磚引玉,見諒。其餘API與Web Worker的融合使用也是大同小異,你們能夠本身琢磨琢磨。

終止 web worker

若是你想當即終止一個運行中的 worker,能夠調用 worker 的terminate()方法。被終止的Worker對象不能被重啓或重用,咱們只能新建另外一個Worker實例來執行新的任務。

myWorker.terminate();

 

處理錯誤

當 worker 出現運行時錯誤時,它的onerror事件處理函數會被調用。它會收到一個實現了ErrorEvent接口名爲error的事件,供開發者捕捉錯誤信息。下面的代碼展現瞭如何綁定error事件:

worker.addEventListener("error", function(evt){ alert("Line #" + evt.lineno + " - " + evt.message + " in " + evt.filename); }, false);  

如上可見, Worker對象能夠綁定error事件;並且evt對象中包含錯誤所在的代碼文件(evt.filename)、錯誤所在的代碼行數(evt.lineno)、以及錯誤信息(evt.message)。

 

下面上一個完整的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>

這個HTML頁面中有個startWorker按鈕,點擊後會運行一個Javascript文件。上面的代碼中首先檢測當前瀏覽器是否支持Web Worker,不支持的話就顯示提醒信息。

按鈕的點擊事件中建立了Worker對象,並給它指定了Javascript腳本文件——demo_workers.js(稍後會有代碼),而且給Worker對象綁定了一個「message」事件。該事件會在後臺代碼(demo_workers.js)向頁面返回數據時觸發。「message」事件能夠經過event.data來獲取後臺代碼傳回的數據。最後,postMessage方法正式執行demo_workers.js,該方法向後臺代碼傳遞參數,後臺代碼一樣經過message事件參數的data屬性獲取。

demo_worker.js

addEventListener('message',function (event) {
    var count = event.data; var interval = setInterval(function () { postMessage(count--);!count && clearInterval(interval); },1000); });

以上代碼在後臺監聽message事件,並獲取頁面傳來的參數;這裏其實是一個從10到1的倒計時:在message事件被觸發以後,把結果傳給頁面顯示出來。

因此當點擊startWorker按鈕,頁面會在count number: 顯示從10遞減一變爲最終的1,在這10秒鐘內頁面依然能夠響應鼠標鍵盤事件。點擊stopWorker按鈕,web worker 會直接終止,頁面變化顯示會直接中止。

 

嵌入式web 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 的一個優點在於可以執行處理器密集型的運算而不會阻塞 UI 線程。在下面的例子中,worker 用於計算斐波那契數。

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設置爲一個函數,當worker對象調用 postMessage()時該函數會接收到發送過來的信息。(注意,這麼使用並不等同於定義一個同名的全局變量,或是定義一個同名的函數。var onmessage function onmessage 將會定義與該名字相同的全局屬性,可是它們不會註冊可以接收從建立 worker 的網頁發送過來的消息的函數。) 這會啓用遞歸,生成本身的新拷貝來處理計算的每個循環。

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>

網頁建立了一個div元素,ID爲result,用它來顯示運算結果,而後生成worker。在生成worker後,onmessage處理函數配置爲經過設置div元素的內容來顯示運算結果,最後,向worker發送一條信息來啓動它。
注意:chrome下不支持在worker中建立worker、以及dump方法、因此上述代碼能夠在Firefox下運行。因爲文章篇幅過長,關於共享線程shared web worker的介紹將在下篇文章Web Worker javascript多線程編程(二)發佈。

相關文章
相關標籤/搜索