[Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列
第61条解释了异步API怎样帮助我们防止一段程序阻塞应用程序的事件队列。使用下面代码,可以很容易使一个应用程序陷入泥潭。
while(true){}
而且它并不需要一个无限循环来写一个缓慢的程序。代码需要时间来运行,而低效的算法或数据结构可能导致运行长时间的计算。
效率不是js唯一关注的。基于事件的编程的确强加了一些特殊的约束。为了保持客户端应用程序的高度交互性和确保所有传入的请求在服务器应用程序中得到充分的服务,保持事件循环的每个轮次尽可能短是至关重要。否则,事件队列会滞销,其增长速度会超过分发处理事件处理程序的速度。在浏览器环境中,一些代价高昂的计算也会导致糟糕的用户体验,因为一个页面的用户界面无响应多数是由于在运行js代码。
那么,如果你的应用程序需要执行代价高昂的计算你该怎么办呢?没有一个完全正确的答案,但有一些通用的技术可用。也许最简单的方法是使用像Web客户端平台的Worker API这样的并发机制。这对于需要搜索大量可移动距离的人工智能游戏是一个很好的方法。游戏可能以生成大量的专门计算移动距离的worker开始。
var ai=new Worker('ai.js');
这将使用ai.js源文件作为worker的脚本,产生一个新的线程独立的事件队列的并发执行线程。该worker运行在一个完全隔离的状态--没有任何应用程序对象的直接访问。但是,应用程序与worker之间可以通过发送形式为字符串的messages来交互。所以,每当游戏需要程序计算移动时,它会发送一个消息给worker。
var userMove=/* ... */;
ai.postMessage(JSON.stringify({userMove:userMove}));
postMessage的参数被作为一个消息增加到worker的事件队列中。为了处理worker的响应,游戏会注册一个事件处理程序。
ai.onmessage=function(event){
executeMove(JSON.parse(event.data).computerMove);
};
与此同时,源文件ai.js指示worker监听消息并执行计算下一步移动所需的工作。
self.onmessage=function(event){
var userMove=JSON.parse(event.data).userMove;
var computerMove=computeNextMove(userMove);
var message=JSON.stringify({
computerMove:computerMove
});
selft.postMessage(message);
};
function computeNextMove(userMove){
//...
}
不是所有的js平台都提供类似Worker这样的API。而且有时传递消息的开销可能会过于昂贵。另一种方法是将算法分解为多个步骤,每个步骤组成一个可管理的工作块。第48条中搜索社交网络图的工作表算法。
Member.prototype.inNetwork=function(other){
var visited={};
var worklist=[this];
while(worklist.length>0){
var member=worklist.pop();
if(member === other){
return true;
}
}
return false;
};
如果这段程序核心的while循环代价太过高昂,搜索工作很可能会以不可接受的时间运行而阻塞应用程序事件队列。即使我们可以使用Worker API,它也是昂贵或不方便实现的,因为它需要复制整个网络图的状态或在worker中存储网络图的状态,并总是使用消息传递来更新和查询网络。
幸运的是,这种算法被定义为一个步骤集的序列--while循环的迭代。可以通过增加一个回调参数将inNetwork转换为一个匿名函数,并像第64条讲述的,将while循环替换一个匿名的递归函数。
Member.prototype.inNetwork=function(other,callback){
var visited={};
var worklist=[this];
function next(){
if(worklist.length === 0){
callback(false);
return;
}
var member=worklist.pop();
if(member === other){
callback(true);
return;
}
setTimeout(next,0);
}
setTimeout(next,0);
};
这段代码的工作方式,为了替换while循环,这里写了一个局部的next函数,该函数执行循环中的单个迭代然后高度应用程序事件队列来异步运行下一次迭代。这使得在些期间已经发生的其他事件被处理后才继续下一次迭代。当搜索完成后,通过迭代的next来返回,从而有效地完成循环。
要调度迭代,我们使用多数js平台都可用的、通用的setTimeout API来注册next函数,使next函数经过一段最少时间(0毫秒)后运行。这具有几乎立刻将回调函数添加到事件队列上的作用。值得注意的是,虽然setTimeout有相对稳定的跨平台移植性,但通常还有更好的替代方案。例如,在浏览器环境中,最低的超时时间被压制为4毫秒,可以采用一种替代方案,使用postMessage立即压入一个事件。
如果应用程序事件队列的每个轮次中只执行算法的一个迭代。可以调整算法,自定义每个轮次中的迭代次数。这很容易实现,只须在next函数的主要部分的外围使用一个循环计数器。
Member.prototype.inNetwork=function(other,callback){
function next(){
for(var i=0;i<10;i++){
//...
}
setTimeout(next,0);
}
setTimeout(next,0);
};
提示
避免在主事件队列中执行代价高昂的算法
在支持Worker API的平台,该API可以用来在一个独立的事件队列中运行长计算程序
在Worker API不可用或代价昂贵的环境中,考虑将计算程序分解到事件循环的多个轮次中
附录一:Worker
Worker是可以在后台运行的任务,它能够被轻松创建,还能向它的创建者发送消息。只要调用worker()构造函数,指定一个需要运行在worker线程内的脚本,就能创建一个worker。
注意:worker能够产生新的worker,前提是这些worker托管于相同的源内来作为它们的父页面。
Worker线程能够在不干扰UI的情况下执行任务。另外,它能够使用XMLHttpRequest来执行I/O操作,只不过XMLHttpRequest上的responseXML与channel两个属性值始终返回null。
线程安全
Worker接口会生成真正的操作系统级别的线程,如果你不小心,那么并发会对你的代码产生影响。对于web worker来说,与其他线程的通信点会被很小心的控制,这意味着你很难引发并发问题。你没有办法去访问非线程安全的组件或者是DOM,此外还需要序列化对象来与线程交互特定的数据。
worker语法
构造函数
Worker(in DOMString scriptURL);
参数
scriptURL
worker将要执行的脚本的URL。它必须遵守同源策略。
返回值
一个新的Worker对象。
方法
void postMessage(Object message[,sequence<Transferable> transferList]);
参数
message
传输给woker的对象;它将包含于传递给onmessage处理函数的事件对象中的data字段内。可以传递任意值或是经过结构拷贝算法处理过的js对象,即可以包含循环引用。
transferList
一个可选的对象数组,用于转让它们的所有权。如果一个对象的所有权被转让,那么它在原来的上下文内将不可使用,而只能在转让到的worker内可用
void terminate();
立即终止worker。该方法不会给worker留下任何完成操作的机会;就是简单的立即停止。
属性
onmessage EeventListner
一个事件监听函数,每当拥有message属性的MessageEvent从worker中冒泡出来时就会执行该函数。事件的data属性存有消息内容。
onerror EeventListner
一个事件监听函数,每当类型为error的ErrorEvent从worker中冒泡出来时就会执行该函数。
错误信息对象
message 一个可读性良好的错误信息
filename 产生错误的脚本文件名
lineno 发生错误时所在的脚本文件号
[Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列的更多相关文章
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [Effective JavaScript 笔记]第67条:绝不要同步地调用异步的回调函数
设想有downloadAsync函数的一种变种,它持有一个缓存(实现为一个Dict)来避免多次下载同一个文件.在文件已经被缓存的情况下,立即调用回调函数是最优选择. var cache=new Dic ...
- [Effective JavaScript 笔记]第53条:保持一致的约定
对于api使用者来说,你所使用的命名和函数签名是最能产生普遍影响的决策.这些约定很重要具有巨大的影响力.它建立了基本的词汇和使用它们的应用程序的惯用法.库的使用者必须学会阅读和使用这些.一致的约定可以 ...
- [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑
构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...
- [Effective JavaScript 笔记]第46条:使用数组而不要使用字典来存储有序集合
对象属性无序性 js对象是一个无序属性集合. var obj={}; obj.a=10; obj.b=30; 属性a和属性b并没有谁前谁后之说.for...in循环,先输出哪个属性都有可能.获取和设置 ...
- [Effective JavaScript 笔记]第45条:使用hasOwnProperty方法以避免原型污染
之前的43条,44条讨论了属性的枚举,但都没有彻底地解决属性查找中原型污染的问题.看下面关于字典的一些操作 'zhangsan' in dict; dict.zhangsan; dict.zhangs ...
随机推荐
- POJ3903:Stock Exchange(LIS)
题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=87125#problem/E 题目: Description The world ...
- Missing number
Missing number 题目: Description There is a permutation without two numbers in it, and now you know wh ...
- 修改wamp默认网站目录
使用WAMP集成环境,如何更改web根目录 做php开发使用WAMP集成环境的同学大部分有过这样的经历: 如果你试图修改web根目录,那么你肯定会想到要修改apache/apache2.2.11/co ...
- B-树的插入、查找、删除
转自:http://blog.163.com/zhoumhan_0351/blog/static/39954227200910231032917/ 前面讨论的查找都是内查询算法,被查询的数据都在内存. ...
- [读书]10g/11g编程艺术深入体现结构学习笔记(持续更新...)
持续更新...) 第8章 1.在过程性循环中提交更新容易产生ora-01555:snapshot too old错误.P257 (这种情况我觉得应该是在高并发的情况下才会产生) 假设的一个场景是系统一 ...
- VMware+Fedora20+secureCRT7.0配置心得
VMware10 XX版 下载地址 secureCRT7 XX版 下载地址 做好安装等准备工作,打开Fedora虚拟机: 首先.安装openssh-server yum install openssh ...
- AFN----AFNetworking
一.介绍 官方介绍: 1.适用于iOS和Mac OS X两个平台的网络库 2.基于Foundation URL Loading System上进行一套封装 3.提供了丰富的API接口 4.是一个轻量级 ...
- IOS第七天(6:UiTableView编辑模式, 拖动位置 ,滑动删除)
**********UiTableView编辑模式, 拖动位置 ,滑动删除 #import "HMViewController.h" @interface HMViewContro ...
- 无法启动Mysql服务,错误InnoDB: Attempted to open a previously opened tablespace.
2013-08-04 13:48:22 760 [ERROR] InnoDB: Attempted to open a previously opened tablespace. Previous t ...
- jqGrid预定义的格式化类型formatter
下表列出了jqGrid中的预定义格式化类型 所有预定义类型和编辑模式兼容,就是说数字,链接和email等需要转换,才能使他们被正确编辑 类型 选项(默认值参考语言选项) 描述 integer thou ...