【轉】Web Worker javascript多線程編程(一)

來源:互聯網
上載者:User

標籤:name   received   專用   鍵盤事件   圖片   設定   拷貝   計算   def   

原文:https://www.cnblogs.com/peakleo/p/6218823.html

-----------------------------------------------------------------------------------------------------------------------------------------------------------

什麼是Web Worker?

web worker 是運行在背景 JavaScript,不佔用瀏覽器自身線程,獨立於其他指令碼,可以提高應用的總體效能,並且提升使用者體驗。

一般來說Javascript和UI頁面會共用一個線程,在HTML頁面中執行js指令碼時,頁面的狀態是不可響應的,直到指令碼已完成。而這段代碼可以交給Web Worker在後台運行,那麼頁面在Javascript運行期間依然可以響應使用者操作。後台會啟動一個worker線程來執行這段代碼,使用者可以建立多個worker線程。

有兩種 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。

在絕大多數情況下,使用Dedicated web worker就足夠了,因為一般來說在web worker中啟動並執行代碼是專為當前頁面服務的。而在一些特定情況下,web worker可能啟動並執行是更為普遍性的代碼,可以為多個頁面服務。在這種情況下,我們會建立一個共用線程的Shared web worker,它可以被與之相關聯的多個頁面訪問,只有當所有關聯的的頁面都關閉的時候,該Shared web worker才會結束。相對Dedicated web worker,shared web worker稍微複雜些。

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()添加事件監聽器:

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多線程編程(二)發布。

【轉】Web Worker javascript多線程編程(一)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.