公司的产品需要在后端维护着一次会话的状态,而当用户关闭浏览器的时候,需要及时释放资源,这以前是通过浏览器window的load和unload事件分别触发CancelCloseAction和StartCloseAction来实现的。后来发现在有些情况下,比如“杀浏览器进程”,“网络异常”等时候,后台就无法确定浏览器的状态了,以至于不能及时释放资源。所以在另一个产品里,我们就使用了“心跳”监测技术,也就是前端定时(比如15~30秒),向后端发一个心跳,如果后端在特定时间里收不到心跳,就认为浏览器关闭了,此时可以回收该会话的所有资源。一般情况下,这个机制是可以工作的,但某些case,前端Javascript的运算量可能会非常大,这时心跳信号往往被延后,以至于时有session过期的情形出现。每每此时,我们多希望Javascript是多线程的,那该多好啊,可以专门开一个线程来发心跳。
1、封装Web Worker的设想
看了网络上无数的抄来转去,实则内容同样的关于Web Worker的介绍后,我们已经知道,Web Worker的确是真正意义上的多线程了。但看着如此简单的postMessage和onmessage的例子,我实在难以确定该如何使用它,又如何用它来实现想象中的“心跳”线程呢。
另外浏览器兼容问题,IE,万恶的IE依旧特立独行地不支持Web Worker,怎么办? 好消息是从IE8开始,IE开始支持frame间通过postMessage和onmessage来通信了, 这至少可以为模拟一个Web Worker在API层面上提供了一个可能。
Java的java.lang.Thread已经是一个非常好的线程应用模型了。如果把Web Worker包装成一个java.lang.Thread会怎么样呢?我想信,至少对于Java程序员来说估计是很好用的。比如:
- // 1、定义计算任务,可以理解成java.lang.Runnable
-
var task = {
-
context:{}, // 需要计算的东西
-
-
run: function(){
-
// 对context进行计算的方法
-
-
// 如有需要可以向外post消息
-
}
-
};
-
-
// 2、创建线程对象
- var myThread = new js.lang.Thread(task);
-
-
// 3、为线程对象绑定onmessage事件
-
myThead.onmessage = function(e){
-
// 处理线程运行中发出来的消息
-
-
// 如有必要终止线程
-
this.stop();
-
};
-
-
// 4、启动线程
-
myThread.start();
-
-
// 5、如有需要可以向线程提交另一个计算任务
-
myThread.submitTask(task);
从以上js.lang.Thread的设想来看,包装后的Web Worker应该是非常容易用来进行多线程计算的,Thread提供的是计算能力,而数据和计算方法由调用者来决定。
2、Web Worker(Iframe Worker)应该如何工作
在第1步的设想下,Web Worker将被封装在js.lang.Thread里,那么task,即Runnable,需要发送给Worker,而Worker的代码看起来,应该会象这样:
- // onmessage的实际句柄
-
var _onmessage = function(e){
-
-
/**
-
* 我们期望e.data拿到的是如下一样的从var thi$ ...到 }的一个string
-
*
-
* var thi$ = {
-
*
-
* context: {}, // 计算对象
-
*
-
* run : function(){
-
* // 计算方法
-
* }
-
* }
-
*/
-
eval(e.data); // 非常关键
-
-
// 此时,我们有了一个thi$对象,执行计算
-
thi$.run(); //或者 thi$.run.call(thi$);
-
-
};
3、一个实验性的js.lang.Thread实现
Java程序员或着看过我以前文章的人,基本上都可以看懂下面Thread的实现。当然有一些如J$VM,js.lang.Class之类的和我的一个开源项目有关,有兴趣的可以到看一下。
- /**
-
* The
Thread
for easily using Web Worker, and for the
-
* IE8/9 use a iframe simulate Web Worker
-
*
-
* Runnable :{
-
* context: xxx,
-
* run : function,
-
* callback : true/false
-
* }
-
*
-
*/
-
js.lang.Thread = function(Runnable){
-
-
var worker, runnable;
-
var workerType = 0; /* 0: WebWorker, 1: IFrame window for IE */
-
-
var _onmessage = function(e){
-
if(e.getData().source != window &&
-
typeof this.onmessage === "function"){
-
runnable.context = JSON.parse(e.getData().data);
-
this.onmessage(runnable.context);
-
}
-
};
-
-
var _onerror = function(e){
-
if(e.getData().source != window &&
-
typeof this.onerror === "function"){
-
this.onerror(e.getData().data);
-
}
-
};
-
-
/**
-
* Submit new task to the thread
-
*
-
* @param task It should be a
Runnable
or a
-
*
context
in Runnable
-
* @param isRunnable indicates whether the first parameter "task"
-
* is a
Runnable
-
*/
-
this.submitTask = function(task, isRunnable){
-
if(task == undefined || task == null) return;
-
isRunnable = isRunnable || false;
-
-
var context, run, callback;
-
if(isRunnable){
-
context = task.context;
-
run = task.run;
-
callback = task.callback;
-
}else{
-
context = task;
-
run = runnable.run;
-
callback = runnable.callback;
-
}
-
-
var buf = new js.lang.StringBuffer();
-
buf.append("var thi$ = {");
-
buf.append("context:").append(JSON.stringify(context));
-
buf.append(",run:").append(run);
-
buf.append(",callback:").append(callback);
-
buf.append("}");
-
-
var msg = buf.toString();
- //J$VM.System.err.println("Thread post msg: "+msg);
-
if(workerType == 0 ){
-
worker.postMessage(msg);
-
}else{
-
worker.postMessage(msg, "*");
-
}
-
};
-
-
/**
-
* Start the thread
-
*/
-
this.start = function(){
-
this.submitTask(runnable, true);
-
};
-
-
/**
-
* Stop the thread
-
*/
-
this.stop = function(){
-
switch(workerType){
-
case 0:
-
worker.terminate();
-
break;
-
case 1:
-
break;
-
}
-
};
-
-
var _init = function(Runnable){
-
runnable = Runnable || {
-
context:{},
-
run:function(){
-
thi$.context.result = "Demo is OK";
-
},
-
callback:ture};
-
-
var E = js.util.Event;
-
var path = J$VM.env["j$vm_home"]+"/classes/js/util/";
-
-
if(J$VM.isWebWorker){
-
worker = new Worker(path+"Worker.js");
-
E.attachEvent(worker, "message", 1, this, _onmessage);
-
E.attachEvent(worker, "error", 1, this, _onerror);
-
}else{
-
// iframe for IE ?
-
workerType = 1;
-
var iframe = document.createElement("iframe");
-
iframe.style.cssText = "visibility:hidden;";
-
document.body.appendChild(iframe);
-
var text = "" +
-
"" +
-
""+
-
"";
-
var doc = iframe.contentDocument, head, script;
-
doc.open();
-
doc.write(text);
-
doc.close();
-
-
head = doc.getElementsByTagName("head")[0];
-
text = js.lang.Class.getResource(J$VM.env["j$vm_home"]+"/jsre.js");
-
script = doc.createElement("script");
-
script.type = "text/javascript";
-
script.id = "j$vm";
-
script.setAttribute("classpath","");
-
script.text = text;
-
text = js.lang.Class.getResource(path + "Worker.js");
-
script.text += text;
-
head.appendChild(script);
-
head.removeChild(script);
-
-
worker = iframe.contentWindow;
-
E.attachEvent(worker, "message", 0, this, _onmessage);
-
E.attachEvent(worker, "error", 0, this, _onerror);
-
}
-
};
-
-
_init.$bind(this)(Runnable);
-
-
}.$extend(js.lang.Object);
4、Worker.js的实现
Worker.js是Web Worker或IFrame Worker需要加载来处理onmessage的,在我的实现中,它并不是象js.lang.Thread一样,是一个通常意义的的类,而是简单的一个javascript文件,只是被管理在js.util的这个目录下:
- var isWebWorker = function(){
-
try{return (window) ? false : true;} catch (x) {return true;}
-
}();
-
-
var _onmessage = function(e){
-
if(isWebWorker){
-
eval(e.data);
-
}else{
-
var _e = e.getData();
-
if(_e.source == window) return;
-
-
eval(_e.data);
-
}
-
-
if(typeof thi$.run == "function"){
-
thi$.run.call(thi$);
-
-
if(thi$.callback){
-
if(isWebWorker){
-
postMessage(JSON.stringify(thi$.context));
-
}else{
-
window.postMessage(JSON.stringify(thi$.context), "*");
-
}
-
}
-
}
-
};
-
-
if(isWebWorker){
-
importScripts("../../../jsre.js");
-
onmessage = _onmessage;
-
}else{
-
js.util.Event.attachEvent(window, "message", 0, this, _onmessage);
-
}
5、结论
以上代码,已经发布到上,对IE8+, Firefox, Chrome浏览器进行过测试,工作正常。
以后在Javascript里,终于可以象在Java里一样使用多线程计算技术了,当然Web Worker本身的限制是不可避免的,比如不能访问DOM啦。
阅读(6305) | 评论(0) | 转发(0) |