mozilla
您的搜尋結果

    使用 web workers

    Web Workers 提供一條簡單的方法讓網頁在背景執行緒(Thread)中執行程式。一個worker可以藉由傳送訊息到web worker創造端設定的事件處理器來和這個創造端產生的任務傳遞訊息。然而,web workers其實是在不同於目前視窗(window)的另一個全域環境下工作的(透過window來取得目前worker所在的全域物件將會導致錯誤,正確應該透過self)。

    Worker的執行緒可以執行任務而不干擾使用者介面運行,另外,Worker也可以利用XMLHttpRequest執行輸出/輸入(但是responseXML 和channel這兩個屬性為null)

    請參照Worker文件來了解更多有關worker的事項;本文主要在為該文件補充一些例子以及細節部分。如果想知道worker下可調用的函數(functions),請參照Functions available to workers

    有關執行緒安全(Thread Safety)

    Worker介面(Interface)是真的產生OS層級的執行緒,如果不注意同步問題,程式是會出現一些有趣的效果。儘管如此,web worker不容易出現同步問題,因為各執行緒間的溝通點都已經被小心的控管了,你無法存取非執行緒安全的元件或DOM,而你也必須用序列化(serialized)的物件來和執行緒傳遞資料。

    創造一個worker

    創造一個worker相當容易,所要做的只有呼叫Worker()建構子,指定Worker執行緒要執行的程式腳本所在的URI,若是希望接受來自worker的通知,可以在worker的onmessage屬性上註冊一個適當的事件處理函數(event handler function)。

    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執行緒。

    Note傳入Worker建構子的URI必須遵守same-origin政策。目前各家瀏覽器在甚麼樣的URI符合same-origin的認定上有所不一,Gecko 10.0 (Firefox 10.0 / Thunderbird 10.0 / SeaMonkey 2.7)之後的瀏覽器允許data URI,而IE 10則不允許Blob URI。

    傳遞資料

    主頁和worker間的資料傳遞乃是靠複製而非分享。物件被序列化以傳給worker,worker之後再去序列化(de-serialized),產生一份該物件的複製於worker端。大部分的瀏覽器以structured cloning來實現這項特色。

    接下來的例子將建立一個emulateMessage函數來模擬這個複製而非分享的資料傳遞特色。

    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

    所謂訊息就是一個被複製(非分享)的值。

    訊息(message)能夠透過postMessage()在worker和主頁間傳遞,而messge事件的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 (the worker):

    postMessage("I\'m working before postMessage(\'ali\').");
    
    onmessage = function (oEvent) {
      postMessage("Hi " + oEvent.data);
    };
    Note: 背景執行緒,包含worker,無法操作DOM,如果背景執行緒的執行結果需要改變DOM,必須傳遞訊息回主頁,讓主頁做DOM改變。

    Structured cloning演算法能夠接受JSON,甚至JSON不允許的環形參照(circular references)。

    傳遞資料例子

    例子1: 產生一個通用的”非同步eval()

    底下展示如何於worker內藉由eval()非同步執行Javascrip程式碼。

    // 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 (主頁):

    <!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間訊息內容的可行方法。

    轉送傳遞資料所有權(Transferable object)

    Google Chrome 17和Firefox 18有另外一個方法可以高效能地和worker傳遞另一種物件(Transferable Objects);Transferable object從一個環境傳送到另一個環境時不用複製,這代表當傳送大型資料時可以顯著提高效能。試想C/C++的pass-by-reference,而不像pass-by-reference的是,當傳送後,被傳送物件的所有權將被移轉到新環境,例如,當由主頁傳送ArrayBuffer到worker端時原始的ArrayBuffer會被清除,然後傳送到worker端(更多資訊:Transferable Objects: Lightning Fast!)。

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

    產生 subworker

    Worker可以產生其他worker(subworker),subworker的來源也必須和主頁相同,另外,subworker的URI的解析是相對於父worker的位置而非所在頁面,這項特色有助於追蹤worker間的相依性。

    Subworker現階段Chrome仍不支援(crbug.com/31666)。

    內嵌式worker

    目前並沒有方法如同<script>一般”正式”地內嵌worker程式碼於網頁中,不過若是<script>元素沒有src屬性而且type屬性的MIME type設成不可執行,哪麼該<script>元素會被視為資料區塊元素(Data Block),HTML5的Data Block可以存放幾乎任何文字資料,利用這一特色,我們可以內嵌worker程式碼如下:

    <html>
    <head>
    <meta charset="UTF-8" />
    <title>MDN Example - Embedded worker</title>
    <script type="text/js-worker">
      // This script WON'T be parsed by JS engines because its mime-type is text/js-worker.
      var myVar = "Hello World!";
      // Rest of your worker code goes here.
    </script>
    <script type="text/javascript">
      // This script WILL be parsed by JS engines because its mime-type is text/javascript.
      function pageLog (sMsg) {
        // Use a fragment: browser will only render/reflow once.
        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">
      // This script WON'T be parsed by JS engines because its mime-type is text/js-worker.
      onmessage = function (oEvent) {
        postMessage(myVar);
      };
      // Rest of your worker code goes here.
    </script>
    <script type="text/javascript">
      // This script WILL be parsed by JS engines because its mime-type is text/javascript.
    
      // In the past...:
      // blob builder existed
      // ...but now we use Blob...:
      var blob = new Blob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function (oScript) { return oScript.textContent; }),{type: "text/javascript"});
    
      // Creating a new document.worker property containing all our "text/js-worker" scripts.
      document.worker = new Worker(window.URL.createObjectURL(blob));
    
      document.worker.onmessage = function (oEvent) {
        pageLog("Received: " + oEvent.data);
      };
    
      // Start the worker.
      window.onload = function() { document.worker.postMessage(""); };
    </script>
    </head>
    <body><div id="logDisplay"></div></body>
    </html>

    內嵌式worker現在存在於新定義的document.worker屬性。

    Timeouts與intervals

    Worker一樣也可以使用Timeouts與intervals,所以你能夠讓worker程式碼週期性地執行。

    請參照setTimeout() , clearTimeout() , setInterval() , clearInterval()JavaScript Timers

    終止worker

    呼叫worker的terminate() method可以終止worker。

    myWorker.terminate();

    Worker的執行緒立即被終止,無法完成工作或後續的終止清理程序。

    呼叫nsIWorkerScope.close() method Worker也可以自己終止自己。

    self.close();

    錯誤處理

    當執行時期的錯誤發生時,onerror事件處理器會被呼叫,onerror事件處理器會收到一的名為error的事件物件(實作ErrorEvent Interface),該事件不會bubble且可取消,如果要避免事件預設行為,可以呼叫preventDefault() method。

    以下三個部分是錯誤事件較有趣的地方:

    message
    人可讀的錯誤訊息
    filename
    錯誤發生所在的檔案名稱
    lineno
    錯誤發生所在的行數

    存取navigator物件

    Worker能夠存取其所在環境的navigator物件,該navigator物件同樣含有瀏覽器資訊: appName, appVersion, platform, userAgent

    引入程式碼腳本與libraries

    Worker執行緒能存取一個全域函數(global function), importScripts()。importScripts()可以讓worker端引入程式碼腳本與libraries,importScripts()可接收零到數個要被輸入資源的URI,底下為正確範例:

    importScripts();                        /* imports nothing */
    importScripts('foo.js');                /* imports just "foo.js" */
    importScripts('foo.js', 'bar.js');      /* imports two scripts */
    

    瀏覽器會載入並執行每個程式碼腳本,然後worker能夠存取程式碼腳本內定義的全域變數,若是腳本無法載入,會產生一個NETWORK_ERROR,後續的程式碼不會被執行,不過雖然如此,先前執行過的程式碼或用window.setTimeout()延遲執行的程式碼依然有效,而importScripts()之後寫的程式碼也一樣存在,因為這些程式碼總是在其他程式碼之前就解析過了。

    Note: 雖然程式碼腳本的下載順序不一定,但執行順序會遵照傳入importScripts()的順序,這是同步完成的,importScripts()不會返回直到所有的程式碼都下載並執行完。

    範例

    本部分提供一些使用DOM worker的範例

    在背景進行運算

    Worker的一個好處是進行處理器密集型的運算而不阻擋到使用者介面執行緒,本例中的worker會運算Fibonacci數列。

    JavaScript 程式碼

    下列程式碼存放於”Fibonacci.js”,這個”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 onmessge與function message會定義這些名稱的全域屬性,但他們不會註冊用來接受產生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”,這個div會被用來呈現結果,接下來產生worker,之後,設定onmessage處裡函數來設定div內容好呈現結果,而onerro處裡函數則會列出(dump)錯誤訊息。

    最後,訊息會傳送到worker開始運算工作。

    試一下本範例

    在背景執行web I/O

    請參照文章,Using workers in extensions

    分割工作給多個worker

    隨著多核心電腦越來越普及,劃分複雜運算的工作給多個worker將可以利用這些多核心處理器。

    在worker內產生另一個worker

    上面的Fibonacci範例展示了worker內可以生成其他worker,這讓產生遞迴的程序變得簡單。

    瀏覽器相容性

    Feature Chrome Firefox (Gecko) Internet Explorer Opera Safari (WebKit)
    Dedicated workers 3 3.5 (1.9.1) 10 10.60 4
    Shared workers 5 --- --- 10.60 5
    Passing data using structured cloning 13 8 10 11.50 5.1
    Passing data using  transferable objects 17 webkit 18 --- --- ---
    Feature Android Chrome for Android Firefox Mobile (Gecko) IE Phone Opera Mobile Safari Mobile
    Dedicated workers --- 0.16 3.5 (1.9.1) --- 11 5
    Shared workers --- Not supported --- --- --- ---
    Passing data using structured cloning --- 0.16 8 --- --- ---
    Passing data using  transferable objects ---   18 --- --- ---

    參照

    Document Tags and Contributors

    Contributors to this page: foxbrush, walkingice
    最近更新: walkingice,