Web Worker javascript多线程编程(一)

摘要:
webworker是运行在后台的JavaScript,不占用浏览器自身线程,独立于其他脚本,可以提高应用的总体性能,并且提升用户体验。一般来说Javascript和UI页面会共用一个线程,在HTML页面中执行js脚本时,页面的状态是不可响应的,直到脚本已完成。而这段代码可以交给WebWorker在后台运行,那么页面在Javascript运行期间依然可以响应用户操作。有两种WebWorkerWebworkers可分为两种类型:专用线程dedicatedwebworker,以及共享线程sharedwebworker。第三行为worker设置了message事件的监听函数。每个脚本中的全局对象都能够被worker使用。

什么是Web Worker?

web worker 是运行在后台的 JavaScript,不占用浏览器自身线程,独立于其他脚本,可以提高应用的总体性能,并且提升用户体验。

一般来说Javascript和UI页面会共用一个线程,在HTML页面中执行js脚本时,页面的状态是不可响应的,直到脚本已完成。而这段代码可以交给Web Worker在后台运行,那么页面在Javascript运行期间依然可以响应用户操作。后台会启动一个worker线程来执行这段代码,用户可以创建多个worker线程。

有两种 Web Worker

Webworkers可分为两种类型:专用线程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中运行的代码是专为当前页面服务的。而在一些特定情况下,webworker可能运行的是更为普遍性的代码,可以为多个页面服务。在这种情况下,我们会创建一个共享线程的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你可以在前台做一些小规模分布式计算之类的工作,不过WebWorker有以下一些使用限制:

  • WebWorker无法访问DOM节点;
  • WebWorker无法访问全局变量或是全局函数;
  • WebWorker无法访问window、document之类的浏览器全局变量、方法;

不过WebWorker作用域中依然可以使用有:

  • 定时器相关方法 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 = newXMLHttpRequest();
        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() {
        varresult =document.querySelector('#result'),
                startWorker =document.querySelector('#startWorker'),
                endWorker =document.querySelector('#endWorker'),
                worker,
                data = 10;
        startWorker.addEventListener('click', function(event) {
            if(typeofWorker !== 'undefined') {
                if(typeofworker == "undefined") {
                    worker = newWorker('./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。
  varmyVar = "Hello World!";
  //剩下的 worker 代码写到这里。
</script>
<script type="text/javascript">
  //该脚本会被 JS 引擎解析,因为它的 mime-type 是 text/javascript。
  functionpageLog (sMsg) {
    //使用 fragment:这样浏览器只会进行一次渲染/重排。
    varoFragm =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...:
  varblob = newBlob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function(oScript) { returnoScript.textContent; }),{type: "text/javascript"});

  //创建一个新的 document.worker 属性,包含所有 "text/js-worker" 脚本。
document.worker = newWorker(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 =[];
functionresultReceiver(event) {
  results.push(parseInt(event.data));
  if (results.length == 2) {
    postMessage(results[0] + results[1]);
  }
}
functionerrorReceiver(event) {
  throwevent.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>
    varworker = newWorker("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");
      throwerror;
    };
    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多线程编程(一)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇向 Git 服务器添加 SSH 公钥.NET 轻量级 ORM 框架下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

ie与火狐中常见的一些兼容问题

1. document.form.item 问题 (1)现有问题: 现有代码中存在许多 document.formName.item("itemName") 这样的语句,不能在Firefox(火狐)下运行 (2)解决方法: 改用 document.formName.elements["elementName"] 2. 集合类对象问题 (1)现有问题: 现...

记一次调bug的过程:windows下查找java应用程序CPU与内存过高

最近写了一个多线程程序,并发量峰值有五六千,甚至八九千个线程。经过几番调试,程序终于能够正常运行起来了,而实际上“正常运行”的背后却是“暗藏玄机”。在程序运行4、5个小时之后,会发现电脑机箱非常热,风扇转地异常快。打开任务管理器,发现程序的CPU飙到了90%上下,内存占用4G左右。程序看似正常,但检查log文件就会发现有问题,数据丢失地非常多。 我猜测可能...

chrome扩展程序开发之在目标页面运行自己的JS

如何在页面中嵌入自己写的Javascript脚本呢?下面分别介绍一下在Chrome和Firefox两种浏览器上的操作步骤: Chrome: 1. 打开chrome扩展程序页 – chrome://extensions 2. 将刚才的自定义脚本保存为以user.js为后缀的 .js文件,例如test.user.js,拖入扩展程序页。 3. 重启浏览器。 4....

火狐浏览器(firefox)中js要注意的问题

1.出现错误调用error方法或者ajax请求了多次,那么极有可能是异步请求的原因。 添加async : false , ->async. 默认是 true,即为异步方式,$.ajax执行后,会继续执行ajax后面的脚本,直到服务器端返回数据后,触发$.ajax里的success方法,这时候执行的是两个线程。 -> async 设置为 fals...

SpringMVC的孪生兄弟WebFlux

一、入门文字介绍 官方口水话简短翻译:   Spring WebFlux是一个非阻塞的Web框架,用于利用多核,短时间可一处理大量并发连接。 非阻塞式   在servlet3.1提供了非阻塞的API,WebFlux为之有自己的一套手段,   使用非阻塞的方式可以利用较小的线程或硬件资源来处理并发 函数式编程端点   Spring5必须配备java8,函数式...

JS动态创建表格比较【转】

目标:生成一个2000*5的表格,每个单元格的内容是行号+逗号+列号 方法一:使用createElement生成表格,使用insertRow和insertCell方法生成行列,单元格的内容使用innerHTML属性进行填充。 方法二:使用createElement生成表格,使用CreateElement方法生成行列,单元格的内容使用了createTex...