赵工的个人空间


专业技术部分转网页计算转业余爱好部分


  网站建设

首页 > 专业技术 > 网站建设 > HTML5 Workers多线程
HTML5 Workers多线程

一直以来,Web应用程序只能在单一的线程中运行,如果需要Web应用程序进行复杂运算等需要长时间处理情况,会导致Web页面长时间处于无法响应的状态,还会因为超时而无法处理。
HTML5新增Web Workers,可以在后台创建一个运行脚本的线程,该线程的运行独立于任何页面。使用Web Workers创建的独立的线程,可以长时间地去执行一个任务,在线程执行任务期间,不会因为用户的单击操作或其他用户界面的交互行为而中断,也不会因为线程还在执行中造成用户界面无法响应操作。
HTML5增加了线程处理功能,提高了JavaScript的编程能力。使用JavaScript编写多线程比较简单,因为浏览器帮助做了很多工作。
Web Workers线程不能操作window对象和document对象,如果在线程文件中使用了window对象或document对象,则会发生错误。 Web Workers线程在本质上属于系统线程,是线程执行的一种方式。Web Workers包含了两种线程,一种是专属线程Dedicated Worker,另一种是共享线程Shared Worker。两种线程有着不同的用途,并且使用不同的接口来实现。专属线程通常在一个页面中创建,并且该线程只能同创建它的页面进行通信;而共享线程通常是由其中的一个页面创建,其他页面也可以连接到该线程,使用该线程中的资源和信息。
Web Workers规范中,不仅包含了专属线程和共享线程的接口,而且包含了与线程处理相关的错误处理接口,以及线程使用的导航、本地属性和工作单元等接口。

一、专属线程Dedicated Worker:

专属线程通常是由创建它的页面负责与之通信,专属线程使用的是Worker对象。
专属线程的用法非常简单,只需要在页面中创建一个Worker对象,并指定线程脚本文件即可。线程通信,使用postMessage()方法和message事件。

1.检测浏览器的支持情况:

在使用Worker对象之前,需要检测浏览器是否支持该特性。检测方法:
if (typeof Worker==="undefined") {
   alert("浏览器不支持Web Workers");
}
由于专属线程使用的是Worker对象,因此只需要使用typeof运算符来返回window对象的Worker属性即可。如果浏览器不支持专属线程处理,则返回的是undefined。

2.创建专属线程:

使用Worker对象创建专属线程时,需要为Worker对象的构造函数提供一个JavaScript脚本文件的URL地址,该脚本文件中包含了线程中所要执行的代码。脚本文件的URL地址,可以是相对地址或者绝对地址,但该URL地址受浏览器的同源策略限制。
 var worker=new Worker(worker.js);
实例化Worker对象,即创建了一个专属线程,worker.js是线程执行的脚本文件。

3.给线程添加监听消息事件:

如果线程中有消息反馈,可以通过添加Worker对象的message事件来监听从线程发来的消息。示例:
worker.addEventListener('message', function(e) {
   //处理线程worker发来的消息
}; true);
也可以使用Worker对象的onmessage事件句柄的方法:
worker.onmessage= function(e) {
   //处理线程worker发来的消息
}

4.向线程中发送消息:

使用Worker对象的postMessage()方法,可以向线程中发送消息。示例:
worker.postMessage("Zhao") ;
这里假设向线程中发送一个Zhao字符串。

5.编写线程处理的脚本文件:

在后台的线程中是不能访问页面文档或窗口对象的,如果使用了window对象或document对象则会发生错误。线程的脚本这样处理:当收到页面发来的消息后,处理消息并把反馈的结果发回页面。
由于线程的脚本文件中涉及消息的接收与发送,因此仍然使用了postMessage()方法和message事件。示例:
onmessage=function(e) {
   var val="Hello,";
   postMessage(value.data);
}
通过在文件中直接添加message事件,即可监听页面发来的消息,直接使用postMessage()方法即可向页面发送消息。

6.在线程中加载多个文件:

由于在后台的线程中不能访问document对象,因此不能采用包含<script>元素的方式来加载相关的脚本文件。Web Workers提供了另外一种导入其他文件的方法--importScript:
importScript("xxx.js");
导入的脚本文件只会在已有的Worker对象中加载和执行,也可以同时导入多个脚本文件:
importScript("xxx.js","yyy.js");

7.监听线程错误:

如果执行的线程出现错误,无法直接反馈到页面上。可以给Worker对象添加错误监听事件,来监听线程中出现的错误。特别是调试时,使用这种方法监听事件非常有效。可以给Worker
对象添加error事件以监听线程中的错误。示例:
worker.addEventListener('error',function(e) {
   console.warn(e.message, e);
}, true);
也可以使用Worker对象的onerror事件句柄的方法:
worker.onerror=function(e) {
   console.warn(e.message, e);
}

一个示例:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
  <title>Web Workers </title>
<script type="text/javascript">
function greeting() {
   if (typeof Worker=="undefined") {
      console.log("浏览器不支持Web Workers");
      return;
   }
   var worker=new Worker("worker.js");
   worker.onmessage=function(e) {
      document.getElementById("msg").innerText=e.data;
   }
   worker.onerror=function(e) {
      console.warn(e.message, e);
   }
   var val=document.getElementById("Name").value;
   worker.postMessage(val);
}
</script>
</head>
<body>
<input type="text" id="Name" value="" />
<input type="button" value="欢迎辞" onClick="greeting()" />
<span id="msg"></span>
</body>
<html>
线程脚本文件worker.js:
onmessage=function(e) {
   Var val="Hello, ";
   postMessage(val+e.data);
}

8.线程嵌套:

线程中可以嵌套线程,这样就可以把较大的线程处理分解成多个子线程,在每个线程中各自完成相对独立的部分。
线程嵌套的方法,就是在一个线程中创建另外一个子线程。方法:
var subworker=new Worker('subworker.js');
这样,可以像操作在页面中创建的主线程一样,去操作这个子线程,如添加消息监听事件和错误监听事件等。
有些支持Web Workers线程的浏览器,不一定也支持线程嵌套,使用线程嵌套之前有必要检查一下浏览器是否支持。
多个线程的嵌套有两种方式:单层线程嵌套和多层线程嵌套。

1)单层线程嵌套:

单层线程嵌套,主要是在一个主线程中,创建另外一个子线程,以完成独立的工作。由于是多个线程同时处理,因此执行起来非常高效。
示例:主线程中提供一组数字,求出小于它们的最大素数,求素数的功能由子线程执行
网页部分:
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
  <title>线程嵌套</title>
</head>
<body>
<p>结果:<output id="result"></output></p>
<script type="text/javascript">
 var worker=new Worker("worker.js");
 worker.onmessage=function(evt) {
   document.getElementById("result").innerHTML+="<br />"+JSON.stringify(evt.data);
}
 worker.onerror=function(e) {
   console.log(e.message);
   worker.terminate();
}
</script>
</body>
<html>
主线程文件worker.js:
var num_workers=10;
var step=1000;
if (typeof Worker!=="undefined") {
   for (var i=0;i<num_workers;i+=1) {
      var subworker=new Worker('subworker.js');
      subworker.onmessage=storageResult;
      subworker.postMessage(i*step);
   }
} else {
   postMessage("浏览器不支持线程嵌套!");
}
function storeResult(e) {
   postMessage("e.data");
}
子线程文件subworker.js:
onmessage=function(e) {
   var num=1*e.data+1;
   search: while (num>1) {
      num--;
      isprime=false;
      for (var i=2,x=Math.sqrt(num); i<=x; i++) {
         if (num%i==0) {
            continue search;
         }
      }
      postMessage(e.data+"以内的最大素数是:"+num);
      break;
   }
}
主线程连续创建了10个子线程,并分别添加了监听消息事件。消息返回的顺序与线程创建的顺序是不一致的,说明创建的这些线程是并行执行的。

2)多层线程嵌套:

多层线程嵌套,主要是指主线程中会创建多个子线程,并且在子线程间进行数据交互。多层线程嵌套在代码编写方面是层层嵌套的。
要在多层线程嵌套中实现子线程之间的数据交互,要遵循如下步骤:
·在主线程中创建子线程,可选择向该子线程发送数据
·执行子线程中的任务,并把执行结果发回主线程
·主线程监听到上一个子线程发来的消息数据后,即创建下一个子线程,并把上一个子线程发来的消息传递给下一个子线程,然后再执行下一个子线程,如此循环嵌套。
·主线程接收最后一个子线程发来的消息。
这样,就实现了多层线程嵌套,示例:
var subworker=new Worker('subworker.js');
subworker.postMessage(100);
subworker.onmessage=function(e) {
   var subworker2=new Worker('subworker2.js');
   subworker2.postMessage(e.data);
   subworker2.onmessage=function(e) {
      postMessage(e.data);
   }
}
多层线程嵌套,并不是在子线程中再创建子线程,而是所有的子线程都是在主线程中以嵌套的方式创建的,并实现一个子线程向另一个子线程传递数据。

多层线程示例:

示例中会用到5个文件,数据文件xhr.txt、页面文件、主线程文件worker.js、异步请求子线程文件xhrworkers.js、数据处理子程序文件rangeworker.js。主页面创建一个主线程,主线程再创建一个用于向服务器发起异步请求的子线程,并在该子线程的消息监听事件中创建另一个子线程,用于处理异步请求获得的数据。
页面文件:
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
  <title>使用线程处理异步请求的数据</title>
<style type="text/css">
output {color:#006699;border:1px solid #cccccc;padding:5px;display:block;}
</style>
</head>
<body>
<p>处理结果:<output id="result"></output></p>
<script type="text/javascript">
var worker=new Worker("worker.js");
worker.onmessage=function(evt) {
   var json=JSON.parse(evt.data);
   var obj=document.getElementById('result');
   obj.innerHTML+="最大值:"+json.maxValue;
   obj.innerHTML+=";最小值:"+json.minValue;
}
worker.onerror=function(e) {
   console.log(e.message);
   worker.terminate();
}
</script>
</body>
<html>
主线程文件worker.js:
var xhrWorker=new Worker("xhrworker.js");
xhrWorker.onmessage=function(e) {
   var rangeWorker=new Worker('rangeworker.js');
   rangeWorker.postMessage(e.data);
   rangeWorker.onmessage=function(e) {
      postMessage(e.data);
   }
}
主线程文件中,首先创建一个xhrWorker子线程,负责异步请求服务器数据;其次在xhrWorker线程的消息监听事件中创建另外一个rangeWorker子线程,用于接收数组数据并处理;最后在rangeWorker子线程的消息监听事件中向页面发送接收到的消息。
子线程文件xhrworker.js:
var xmlHttp=new XMLHttpRequest();
xmlHttp.onerror=function(e) {
   postMessage(null);
}
xmlHttp.onload=function(e) {
   postMessage(xmlHttp.responseText);
   close();
}
xmlHttp.open("get","xhr.txt?"+(new Date()).getTime(), true);
xmlHttp.send(null);
子线程xhrworker文件中,向服务器发起异步请求,并将请求到的文件发送给主线程。
子线程文件rangeworker.js:
onmessage=function(e) {
   var arr=JSON.parse(e.data);
   var result={maxValue:0,minValue:0};
   if (arr.length>0) {
      result.maxValue=arr[0];
      result.minValue=arr[1];
   }
   for (var i=0,n=arr.length; i<=n; i++) {
      if (result.maxValue<arr[i]) {
         result.maxValue=arr[i];
      }
      if (result.minValue>arr[i]) {
         result.minValue=arr[i];
      }
   }
   postMessage(JSON.stringify(result));
   close();
}
子线程rangeworker文件中,接收主线程发来的消息,并作为数组进行处理。使用循环的方式求出数组中的最大值和最小值,并将获得的结果放在一个JSON对象中返回。

二、共享线程:

共享线程避免了线程的重复创建和销毁过程,降低了系统性能的消耗。共享线程Shared Worker可以同时有多个页面的线程连接,是由SharedWorker对象创建的。
要创建共享线程,需要在页面中创建一个SharedWorker对象,并指定线程脚本文件,线程通信仍然使用postMessage()方法和message事件。

1.检测浏览器支持情况:

使用SharedWorker对象之前,需要检测浏览器是否支持。检测方法:
if (typeof SharedWorker==="undefined") {
   alert("使用的浏览器不支持共享线程");
}
由于共享线程使用的是SharedWorker对象,因此只需要使用typeof运算符来返回window对象的SharedWorker属性即可。

2.创建共享线程:

使用SharedWorker,也需要提供一个JavaScript脚本文件的URL地址,该脚本文件中包含了线程中所要执行的代码。示例:
var worker=new SharedWorker("sharedworker.js");
脚本文件的URL地址可以是相对地址或者绝对地址,同样受浏览器的同源策略限制。

3.给线程添加监听事件:

共享线程也使用message事件监听线程消息,但使用SharedWorker对象的port属性与线程通信。示例:
worker.port.onmessage=function(e) {   //这里使用port属性
   //处理线程worker发来的消息
}
也可以使用添加事件的方式:
worker.port.addEventListener('message', function(e) {
   //处理线程发来的消息
}, false);
worker.port.start();   //需要启动端口
需要注意,事件是附加在对象的port属性上的,当使用addEventListener函数添加事件时要使用worker.port.start()来启动端口。

4.向线程中发送消息:

使用SharedWorker对象的port属性向共享线程发送消息:
worker.port.postMessage("Zhao");
与共享线程的通信都是由SharedWorker对象的port属性来完成的。

5.编写线程处理的脚本文件:

在共享线程中,首先使用connect事件监听来自不同用户的连接,然后才能在connect事件处理程序中为各个连接建立各自的消息监听和消息发送的通信机制。示例:
Var count=0;
onconnect=function(e) {
   count+=1;
   var port=e.ports[0];
   port.postMessage("欢迎!这是新的链接#"+count);
   port.onmessage=function(e) {
      port.postMessage("你好,"+e.data);
   }
}
在线程文件中,每个连接与线程之间的通信是在connect事件中进行的,所以各个通信之间是相互独立的,但是它们可以共享全局的信息。
另外,共享线程也可以通过importScript来导入其他脚本文件。

示例:提供连接计数器以记录连接数

页面文件:
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
  <title>共享线程</title>
</head>
<body>
<input type="text" value="" onchange="worker.port.postMessage(this.value);return false;" />
<pre id="log">日志:</pre>
</body>
<html>
<script type="text/javascript">
if (typeof SharedWorker==="undefined") {
   alert("使用的浏览器不支持共享线程");
} else {
   var log=document.getElementById('log');;
   var worker=new SharedWorker("sharedworker.js");
   worker.port.addEventListener("message",function(e) {
      log.textContent+="\n"+e.data;
   }, false);
   worker.port.start();
   worker.port.postMessage("初始化");
}
</script>
共享线程的脚本文件sharedworker.js:
var count=0;   //计数器
onconnect=function(e) {
   count+=1;
   var port=e.ports[0];
   port.postMessage("欢迎!这是链接"+count);
   port.onmessage=function(e) {
      port.postMessage("线程收到你的消息:"+e,data);
   }
}

Copyright@dwenzhao.cn All Rights Reserved   备案号:粤ICP备15026949号
联系邮箱:dwenzhao@163.com  QQ:1608288659