web workers_使用Web Workers改善Web应用程序的可用性

web workers

随着Ajax和Web 2.0应用程序的问世,最终用户对Web应用程序的快速响应感到迷恋。 在Web应用程序可以更快地响应之前,必须消除某些瓶颈。 瓶颈包括JavaScript和后台I / O的大量计算,需要从主UI呈现过程中删除这些计算。 输入网络工作者。

发展有关此主题的技能

此内容是用于提高技能的渐进知识路径的一部分。 参阅HTML5基础

Web Workers规范提供了独立于任何用户界面脚本在后台运行脚本的功能。 长时间运行的脚本不会因响应点击或其他用户交互的脚本而中断。 Web Worker允许执行长任务,而不会让步,同时保持页面响应速度。

在Web Workers之前,JavaScript是现代Web应用程序的核心。 JavaScript和与之交谈的DOM本质上是单线程的:在任何给定时间只能执行一个JavaScript方法。 即使您的计算机有四个核心,在进行长时间计算时,它也只会使其中一个核心处于繁忙状态。 例如,如果您计算到达月球的理想轨迹,则您的浏览器将无法渲染显示轨迹的动画,并且无法对用户事件(例如鼠标单击或键盘键入)做出React。 。

Web Workers打破了传统JavaScript单线程模式,并引入了多线程编程模型。 工作程序是独立线程。 具有许多任务要处理的Web应用程序不再需要一一处理任务。 而是,应用程序可以将任务分配给不同的工作人员。

在本文中,了解Web Workers API。 一个实际的示例将引导您完成使用Web Workers快速呈现网页的步骤。

从下面的下载表下载本文中使用的示例的源代码。

基本概念

Web Workers的基本组件是:

工人
在后台运行的新线程不会阻止任何主用户界面脚本。 工人(如这些背景脚本所称)相对较重,不打算大量使用。

一个工作人员可以完成很多工作,包括并行计算,后台I / O和客户端数据库操作。 工作人员不应中断主UI或直接操作DOM; 它应该向主线程返回一条消息,并让主线程更新主UI。

子工
在工作人员中创建的工作人员。 子工作人员必须与父页面位于同一来源。 子工作人员的URI是相对于父工作人员的位置而不是所属页面的位置来解析的。
共享工作者
可以通过多个连接由多个页面使用的工作程序。 共享工作者的工作与普通工作者略有不同。 只有少数浏览器支持此功能。

网络工作者API

本节介绍Web Workers API的基础。

创造工人

要创建新的工作程序,只需将工作程序脚本URI作为唯一参数调用工作程序构造函数。 创建工作程序后,将同时启动一个新线程(或可能取决于浏览器的实现的新进程)。

当工作人员完成工作或遇到错误时,您可以从工作人员处onmessage带有工作实例的onmessageonerror属性的通知。 清单1显示了一个样例工作程序。

清单1.示例工作程序myWorker.js
// receive a message from the main JavaScript thread
onmessage = function(event) {
// do something in this worker
var info = event.data;
postMessage(info + “ from worker!”);
};

如果运行下面的清单2中JavaScript代码,您将得到结果“来自工作人员的Hello World”。

清单2. JavaScript主线程中的worker
// create a new worker
var myWorker = new Worker("myWorker.js");
// send a message to start the worker
var info = “Hello World”;
myWorker.postMessage(info);
// receive a message from the worker
myWorker.onmessage = function (event) {
// do something when receiving a message from worker
alert(event.data);
};

终止工人

工作者是一个线程(或进程,本质上),它是一个资源消耗很高的OS级对象。 当分配给工作程序的任务完成时,或者您只想terminate它,您需要调用工作程序的terminate方法来终止正在运行的工作程序。 工作线程或进程将立即被杀死,而没有机会完成其操作或清理自身。 清单3显示了一个示例。

清单3.终止myWorker
myWorker.terminate();

处理错误

与典型JavaScript代码类似,正在运行的工作程序中可能会发生运行时错误。 要处理错误,您需要为工作程序设置onerror处理程序,如果在工作程序中运行脚本期间发生任何错误,则将调用该处理程序。 该事件不会冒泡,您可以取消它。 为了防止发生默认操作,工作程序可以调用错误事件的preventDefault()方法。

清单4.为myWorker添加错误处理程序
myWorker.onerror = function(event){
console.log(event.message);
console.log(event.filename);
console.log(event.lineno);
}

错误事件具有以下三个字段,可能有助于调试:

  • message :人类可读的错误消息
  • filename :发生错误的脚本文件的名称
  • lineno :发生错误的脚本文件行号

导入脚本和库

importScripts()线程可以访问全局函数importScripts() ,该函数使它们可以将脚本或库导入其作用域。 它接受零个或多个要导入的资源URI作为参数。

清单5.导入脚本
//import nothing
importScripts();
//import just graph.js
importScripts('graph.js');
//import two scripts
importScripts('graph.js', 'controller.js');

使用网络工作者

本节将向您介绍Web Workers的实际使用案例。 该示例涉及呈现一个包含几个基于Dojo的Website Displayer小部件的页面。 这些小部件用于通过iFrame显示网站。 如果没有Web Workers,则必须通过Ajax请求获取小部件定义,然后在单个JavaScript线程中呈现它们。 如果小部件定义包含大量数据,这将非常慢。

该示例创建了一些工作程序来获取窗口小部件定义。 每个工作人员都被分配了一个任务,以获取一个小部件定义,并负责告诉主UI JavaScript线程进行渲染。 因为工人可以并行工作,所以这是一个更快的解决方案。

本示例使用Dojo 1.4。 如果你想在自己的浏览器中运行的例子,下载Dojo库(见相关主题 )和源代码(见下载的资源 ),在这篇文章中使用。 图1显示了示例应用程序的结构。

图1. Web Workers应用程序
“ Web Workers”目录下的目录结构的屏幕快照

在图1中:

  • lib是dojo库。
  • /widgets/WebsiteDisplayer.js是基于dojo的Website Displayer小部件实现。
  • /loadwidget/widgets/widgetDefinition[0....3]是每个“网站显示工具”小部件的定义。
  • /loadwidget/Workers.js是工作程序实现。
  • /loadwidget/XMLHttpRequest.js是一个js库,其中包含创建XMLHttpRequst的方法。
  • /loadwidget/LoadWidget.html是启用了Web Workers的演示的主页,这将是主要JavaScript线程。
  • /loadwidget/LoadWidget-none-web-workers.html是在没有Web Worker的情况下实现的主页。

创建网站显示器小部件

网站显示器小部件是一个非常简单的基于Dojo-TitlePane-dijit的小部件。 它将呈现形式化标题窗格的UI,如图2所示。

图2. Website Displayer小部件
网站显示器小部件的屏幕快照,标题为“加载小部件使用Web Workers”

清单6显示了WebsiteDisplayer.js的代码。

清单6. WebsiteDisplayer.js的内容
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.require("dijit.TitlePane");

dojo.declare("loadWidget.WebsiteDisplayer", [dijit.TitlePane], {
    title: "",
    url: "",
    postCreate: function() {
	var ifrm = dojo.create("iframe", {
           src: this.url,
	    style: "width:100%;height:20%;"
	});
	dojo.place(ifrm, this.domNode.children[1], "first");
	this.inherited(arguments);
	var contentFrame = this.domNode.children[1].children[0];
	if (contentFrame.attachEvent) {
	    contentFrame.attachEvent("onload",
		function() {
		    dojo.publish("frameEvent/loaded");
		}
	    );
	} else {
	    contentFrame.onload = function() {
		dojo.publish("frameEvent/loaded");
	    };
	}
    }
});

创造工人

要实现worker.js,请导入一个名为XMLHttpRequest.js的全局JavaScript文件,其中包含全局方法creatXMLHTTPRequest 。 此方法将返回XMLHttpRequest对象。

工作人员将首先将XMLHttpRequest发送到服务器端,并将小部件定义检索回到主JavaScript线程。 清单7和8显示了一个示例。

清单7. Worker.js的内容
importScripts("XMLHttpRequest.js");

onmessage = function(event) {
  var xhr = creatXMLHTTPRequest();
  xhr.open('GET', 'widgets/widgetDefinition' + event.data + '.xml', true);
  xhr.send(null);
  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
      if (xhr.status == 200 || xhr.status ==0) {
	 postMessage(xhr.responseText);
      } else {
	 throw xhr.status + xhr.responseText;
      }
    } 
  }
}
清单8. widgetDefinition0.xml
<div dojoType="loadWidget.WebsiteDisplayer" title="This is Test Widget 0"
   url="http://www.yahoo.com" ></div>

创建主页

在主页上,您可以:创建多个工作程序; 向工人发送消息并启动工人; 接收工人的信息; 并使用收到的消息来操作主界面。

清单9.主页
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>
            Load widgets with Web Workers
        </title>
        <style type="text/css">
            @import "../lib/dijit/themes/soria/soria.css";
	    @import "../lib/dojo/resources/dojo.css";
	    @import "../lib/dojox/layout/resources/GridContainer.css";
            @import "../lib/dojox/layout/resources/DndGridContainer.css"
        </style>
        <script type="text/javascript" src="../lib/dojo/dojo.js" 
           djConfig="parseOnLoad: true,isDebug:true">
        </script>
        <script>
            dojo.require("dojo.parser");
            dojo.require("dojo.io.script");
	     dojo.require("dojox.layout.GridContainer");
            dojo.require("dijit.layout.LayoutContainer");
            dojo.require("dijit.TitlePane");
            dojo.require("dojox.layout.DragPane");
            dojo.registerModulePath("loadWidget", "../../loadWidget");
            dojo.require("loadWidget.WebsiteDisplayer");
	</script>
	<script type="text/javascript" language="javascript">
            var workersCount = 4;
            var haveLoadedCount = 0;
            var widgetCount = 4;
            var startTime = new Date().getTime();
            var endTime = null;
            var executeTime = 0;
            try {
                for (var i = 0; i < workersCount; i++) {
                    var loadWorker = new Worker("Worker.js");
                    loadWorker.postMessage(i);
                    loadWorker.onmessage = processReturnWidgetDefinition;
					loadWorker.onerror = handleWorkerError;
                }
            } catch(ex) {
                console.log(ex);
            }
			
            function processReturnWidgetDefinition(event) {
                var txt = document.createElement("p");
                txt.innerHTML = event.data;
                var div = document.getElementById("loadingDiv");
                div.appendChild(txt);
                haveLoadedCount++;
                if (haveLoadedCount == widgetCount) {
                    dojo.parser.parse();
                }
            }
			
	     function handleWorkerError(event){
		  console.log(event.message);
	     }
			
            dojo.subscribe("frameEvent/loaded", dojo.hitch(null, handelFrameLoaded));

            function handelFrameLoaded() {
                if (haveLoadedCount == widgetCount) {
                    endTime = new Date().getTime();
                    executeTime = endTime - startTime;
                    dojo.byId("loading").innerHTML = "Loading cost time:" + executeTime;
                }
            }
        </script>
    </head>
	
    <body class="soria">
       <div dojoType="dijit.TitlePane" title="Load widgets with Web Workers" 
          style="border: 2px solid black; padding: 10px;"
        id="main">
            <div id="loadingDiv">
                <div id="loading">
                    Widgets are loading......
                </div>
            </div>
        </div>
    </body>
	
</html>

将该主页嵌入到Web应用程序中并运行它。 结果应如图3所示。

图3.使用Web Workers加载小部件
这三个小部件的屏幕截图,显示了三个不同网站的一部分

若要查看使用Web Workers与不使用Web Workers之间的区别,请分别运行LoadWidget.html和LoadWidget-none-web-workers.html并查看结果。 请注意,由于代码示例处理的数据很少,因此没有Web Workers的页面的运行速度将比具有Web Workers的页面更快。 节省的时间与开始工作的成本相平衡。

使用Web Worker的提示

前面的示例涉及XMLHttpRequest和计算。 它不是很大或复杂。 当您给工作人员执行更复杂的任务(例如处理大型计算)时,它将成为一项非常强大的功能。 在项目中采用这项很酷的技术之前,请阅读以下提示。

无法访问工作程序中的DOM

出于安全原因,工作人员无法直接操作HTML文档。 在同一个DOM上运行的多个线程会在线程安全性方面造成问题。 优点是您不再需要担心工作程序实现中的多线程安全问题。

在培养工人时,这种情况有一些限制。 您不能在worker中调用alert() ,这是调试JavaScript代码的一种非常流行的方法。 您也不能调用document.getElementById() ,因为它只能接收和返回变量(可以是字符串,数组,JSON对象,等等)。

工作人员可用的对象

尽管工作人员无法访问window对象,但它可以直接访问navigator 。 您可以在navigator对象中访问appNameappVersionplatformuserAgent

可以以只读方式访问location对象。 您可以在location对象内部获取hostnameport

如本文示例中所示,还可以在工作程序中启用XMLHttpRequest 。 由于此功能,您可以在worker中添加许多有趣的扩展。

也可用:

  • importScripts()方法(用于访问同一域中的脚本文件)
  • JavaScript对象,例如ObjectArrayDateMathString
  • setTimeout()setInterval()方法

postMessage携带的数据类型

postMessage被大量使用,因为它是主要JavaScript线程与工作人员进行交互的主要方法。 但是,现在可以在postMessage中携带的数据类型仅限于本机JavaScript类型,例如Array,Date,Math,String,JSON等。 对复杂的自定义JavaScript对象的支持不是很好。


翻译自: https://www.ibm.com/developerworks/web/library/wa-webworkers/index.html

web workers

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值