HTML5編程之旅 第5站Web Workers


HTML5 Web Workers能夠使得你的Web應用程序具有後臺處理能力,可以支持多線程,且能夠充分利用多核CPU帶來的優點。以免JavaScript循環持續運行後彈出的警告窗口。css

儘管Web Workers功能強大,可是也存在一些缺點,好比不能直接訪問Web頁面和DOM API、消耗CPU週期且致使系統反應速度變慢。html

若是須要Web應用執行一些後臺數據處理,但又不影響Web頁面自己的交互時,就能夠使用Web Workers,同時須要添加一個監聽器以監聽它發出的消息。git

1、HTML5 Web Workers API概述canvas

    一、瀏覽器支持檢查瀏覽器

typeof(Worker) !== "undefined"

二、建立HTML5 Web Workers
多線程

Web Workers初始化時會接受一個JavaScript文件的URL地址(URL能夠是絕對或者相對路徑,但必須同源),其中包含了供Worker執行的代碼。這段代碼會設置事件監聽器,並與生成Worker的容器進行通訊。併發

worker = new Worker("echoWorker.js");

三、多個JavaScript文件的加載與執行
app

因爲Web Workers沒有訪問document對象的權限,所以不能使用常規導入JavaScript的方式,須要另外一種方法來導入JavaScript,即importScripts函數

importScripts("helper.js","example.js","blur.js");

多個腳本的導入會按照導入順序執行。
post

四、HTML5 Web Workers通訊

Web Workers一旦生成,就能夠使用postMessage API傳送和接收數據。

爲了能與Web Workers成功通訊,除了在主頁(調用Web Workers的頁面)中添加代碼外,Worker JavaScript文件中也要添加相應代碼。

2、Web Workers示例應用

一、創建主頁與Web Workers之間的通訊

首先要添加對postMessage函數的調用,以下所示:

worker.postMessage("發送示例消息");

相應消息發送給Web Workers後,還須要添加監聽器,用以監聽Web Workers發來的消息

worker.addEventListener("message",messageHandler,true);
function messageHandler(e){
    //處理worker發來的消息
}

Web Workers JavaScript文件中,也須要添加上述相似的代碼:必須添加事件監聽器來監聽發來的消息和錯誤消息,而且經過用postMessage函數實現與頁面之間的通訊。

   二、處理錯誤

監聽到錯誤事件發生後,要將出錯信息反饋給用戶:

function errorHandler(e){
    console.log(e.message,e);
}

三、終止Web Workers

worker.terminate();

終止以後的Web Workers不能被從新啓動,但能夠使用一樣的URL建立一個新的Worker

四、示例代碼整合

本示例實現 一個帶有圖像模糊過濾器的Web頁面,其中過濾動做將由多個Web Worker併發執行。

(1)編寫blur.js輔助腳本

function inRange(i, width, height) {
    return ((i>=0) && (i < width*height*4));
}
 
function averageNeighbors(imageData, width, height, i) {
    var v = imageData[i];
 
    // cardinal directions
    var north = inRange(i-width*4, width, height) ? imageData[i-width*4] : v;
    var south = inRange(i+width*4, width, height) ? imageData[i+width*4] : v;
    var west = inRange(i-4, width, height) ? imageData[i-4] : v;
    var east = inRange(i+4, width, height) ? imageData[i+4] : v;
 
    // diagonal neighbors
    var ne = inRange(i-width*4+4, width, height) ? imageData[i-width*4+4] : v;
    var nw = inRange(i-width*4-4, width, height) ? imageData[i-width*4-4] : v;
    var se = inRange(i+width*4+4, width, height) ? imageData[i+width*4+4] : v;
    var sw = inRange(i+width*4-4, width, height) ? imageData[i+width*4-4] : v;
 
    // average
    var newVal = Math.floor((north + south + east + west + se + sw + ne + nw + v)/9);
 
    if (isNaN(newVal)) {
        sendStatus("bad value " + i + " for height " + height);
        throw new Error("NaN");
    }
    return newVal;
}
 
function boxBlur(imageData, width, height) {
    var data = [];
    var val = 0;
    for (var i=0; i<width*height*4; i++) {
        val = averageNeighbors(imageData, width, height, i);
        data[i] = val;
    }
 
    return data;
}

    (2)編寫blur.html頁面

<!DOCTYPE html>
<title>HTML5 Web Workers</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href = "styles.css">
 
<h1>HTML5 Web Workers</h1>
 
<p id="status">你的瀏覽器不支持HTML5 Web Workers.</p>
 
 
<button id="startBlurButton" disabled>模糊</button>
<button id="stopButton" disabled>中止Workers</button>
<button onclick="document.location = document.location;">從新加載</button>
 
<label for="workerCount">Workers數量</label>
<select id="workerCount">
    <option>1</option>
    <option selected>2</option>
    <option>4</option>
    <option>8</option>
    <option>16</option>
</select>
 
<div id="imageContainer"></div>
<div id="logOutput"></div>

(3)編寫blurWorker.js

importScripts("blur.js");
 
function sendStatus(statusText){
postMessage({"type" : "status",
"statusText" : statusText});
 
}
function messageHandler(e){
var messageType = e.data.type;
switch(messageType){
case ("blur"):
sendStatus("Worker開始模糊的數據範圍: " +
                            e.data.startX + "-" + (e.data.startX+e.data.width));
var imageData = e.data.imageData;
imageData = boxBlur(imageData, e.data.width, e.data.height, e.data.startX);
 postMessage({"type" : "處理",
                         "imageData" : imageData,
                         "width" : e.data.width,
                         "height" : e.data.height,
                         "startX" : e.data.startX
                        });
            sendStatus("在數據範圍: " +
                            e.data.startX + "-" + (e.data.width+e.data.startX) + "完成數據模糊");
            break;
        default:
            sendStatus("Worker得到消息:" + e.data);
}
}
 
addEventListener("message", messageHandler, true);

    (4)Web Workers進行通訊

function sendBlurTask(worker, i, chunkWidth) {
        var chunkHeight = image.height;
        var chunkStartX = i * chunkWidth;
        var chunkStartY = 0;
        var data = ctx.getImageData(chunkStartX, chunkStartY,
                                    chunkWidth, chunkHeight).data;
 
        worker.postMessage({'type' : 'blur',
                            'imageData' : data,
                            'width' : chunkWidth,
                            'height' : chunkHeight,
                            'startX' : chunkStartX});
}

(5)示例代碼 

<!DOCTYPE html>
    <title>HTML5 Web Workers</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" href = "styles.css">
     
    <h1>HTML5 Web Workers</h1>
     
    <p id="status">你的瀏覽器不支持HTML5 Web Workers.</p>
     
     
    <button id="startBlurButton" disabled>模糊</button>
    <button id="stopButton" disabled>中止Workers</button>
    <button onclick="document.location = document.location;">從新加載</button>
     
    <label for="workerCount">Workers數量</label>
    <select id="workerCount">
        <option>1</option>
        <option selected>2</option>
        <option>4</option>
        <option>8</option>
        <option>16</option>
    </select>
     
    <div id="imageContainer"></div>
    <div id="logOutput"></div>
    <script>
        var imageURL = "example2.png";
        var image;
        var ctx;
        var workers = [];
        window.addEventListener("load", loadDemo, true);
        function loadDemo(){
            log("加載圖像數據");
            if(typeof(Worker) !== "undefined"){
                document.getElementById("status").innerHTML = "你的瀏覽器支持HTML5 Web Workers";
                 
                document.getElementById("stopButton").onclick = stopBlur;
                document.getElementById("startBlurButton").onclick = startBlur;
                 
                loadImageData(imageURL);
                 
                document.getElementById("startBlurButton").disabled = true;
                document.getElementById("stopButton").disabled = true;
            }
        }
        function log(s){
            var logOutput = document.getElementById("logOutput");
            logOutput.innerHTML = s + "<br>" + logOutput.innerHTML; 
        }
        function loadImageData(url){
            var canvas = document.createElement('canvas');
            ctx = canvas.getContext('2d');
            image = new Image();
            image.src = url;
            document.getElementById("imageContainer").appendChild(canvas);
            image.onload = function(){
                canvas.width = image.width;
                canvas.height = image.height;
                ctx.drawImage(image, 0, 0);
                window.imgdata = ctx.getImageData(0, 0, image.width, image.height);
                n = ctx.createImageData(image.width, image.height);
                setRunningState(false);
                log("圖像加載:" + image.width + "x" + image.height + "pixels");  
            }
        }
        function setRunningState(p){
            document.getElementById("startBlurButton").disabled = p;
            document.getElementById("stopButton").disabled = !p;
        }
        function stopBlur(){
            for(var i=0; i<workers.length; i++){
                workers[i].terminate();  
            }
            setRunningState(false);
        }
        function startBlur(){
            var workerCount = parseInt(document.getElementById("workerCount").value);
            var width = image.width/workerCount;
            for(var i=0; i<workerCount; i++){
                var worker = initWorker("blurWorker.js");
                worker.index = i;
                worker.width = width;
                workers[i] = worker;
                sendBlurTask(worker, i, width);
            }
            setRunningState(true);
        }
        function initWorker(src){
            var worker = new Worker(src);
            worker.addEventListener("message",messageHandler, true);
            worker.addEventListener("error",errorHandler, true);
            return worker;
        }
        function messageHandler(e) {
            var messageType = e.data.type;
            switch (messageType) {
                case ("status"):
                log(e.data.statusText);
                break;
                case ("progress"):
                var imageData = ctx.createImageData(e.data.width, e.data.height);
                 
                for (var i = 0; i<imageData.data.length; i++) {
                var val = e.data.imageData[i];
                if (val === null || val > 255 || val < 0) {
                log("illegal value: " + val + " at " + i);
                return;
                }
                 
                imageData.data[i] = val;
                }
                ctx.putImageData(imageData, e.data.startX, 0);
                 
                // blur the same tile again
                sendBlurTask(e.target, e.target.index, e.target.width);
                break;
                default:
                break;
            }
        }
         
        function errorHandler(e) {
            log("error: " + e.message);
        }
         
        function sendBlurTask(worker, i, chunkWidth) {
                var chunkHeight = image.height;
                var chunkStartX = i * chunkWidth;
                var chunkStartY = 0;
                var data = ctx.getImageData(chunkStartX, chunkStartY,
                                            chunkWidth, chunkHeight).data;
         
                worker.postMessage({'type' : 'blur',
                                    'imageData' : data,
                                    'width' : chunkWidth,
                                    'height' : chunkHeight,
                                    'startX' : chunkStartX});
        }
     
    </script>

源碼請到http://git.oschina.net/niweiwei/HTML5/tree/master/workers查看

相關文章
相關標籤/搜索