关于Web Worker应用的一个想法和实现

6264阅读 0评论2011-11-08 hoodng
分类:系统运维

    公司的产品需要在后端维护着一次会话的状态,而当用户关闭浏览器的时候,需要及时释放资源,这以前是通过浏览器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. // 1、定义计算任务,可以理解成java.lang.Runnable
  2. var task = {
  3.     context:{}, // 需要计算的东西
  4.     
  5.     run: function(){
  6.         // 对context进行计算的方法

  7.         // 如有需要可以向外post消息
  8.     }
  9. };

  10. // 2、创建线程对象
  11. var myThread = new js.lang.Thread(task);

  12. // 3、为线程对象绑定onmessage事件
  13. myThead.onmessage = function(e){
  14.     // 处理线程运行中发出来的消息

  15.     // 如有必要终止线程
  16.     this.stop();
  17. };

  18. // 4、启动线程
  19. myThread.start();

  20. // 5、如有需要可以向线程提交另一个计算任务
  21. myThread.submitTask(task);
    从以上js.lang.Thread的设想来看,包装后的Web Worker应该是非常容易用来进行多线程计算的,Thread提供的是计算能力,而数据和计算方法由调用者来决定。

2、Web Worker(Iframe Worker)应该如何工作
       
    在第1步的设想下,Web Worker将被封装在js.lang.Thread里,那么task,即Runnable,需要发送给Worker,而Worker的代码看起来,应该会象这样:

  1. // onmessage的实际句柄
  2. var _onmessage = function(e){
  3.     
  4.     /**
  5.      * 我们期望e.data拿到的是如下一样的从var thi$ ...到 }的一个string
  6.      *
  7.      * var thi$ = {
  8.      *
  9.      * context: {}, // 计算对象
  10.      *
  11.      * run : function(){
  12.      * // 计算方法
  13.      * }
  14.      * }
  15.      */
  16.     eval(e.data); // 非常关键

  17.     // 此时,我们有了一个thi$对象,执行计算
  18.     thi$.run(); //或者 thi$.run.call(thi$);
  19.   
  20. };

3、一个实验性的js.lang.Thread实现

    Java程序员或着看过我以前文章的人,基本上都可以看懂下面Thread的实现。当然有一些如J$VM,js.lang.Class之类的和我的一个开源项目有关,有兴趣的可以到看一下。

  1. /**
  2.  * The Thread for easily using Web Worker, and for the
  3.  * IE8/9 use a iframe simulate Web Worker
  4.  *
  5.  * Runnable :{
  6.  * context: xxx,
  7.  * run : function,
  8.  * callback : true/false
  9.  * }
  10.  *
  11.  */
  12. js.lang.Thread = function(Runnable){
  13.     
  14.     var worker, runnable;
  15.     var workerType = 0; /* 0: WebWorker, 1: IFrame window for IE */

  16.     var _onmessage = function(e){
  17.         if(e.getData().source != window &&
  18.          typeof this.onmessage === "function"){
  19.             runnable.context = JSON.parse(e.getData().data);
  20.             this.onmessage(runnable.context);
  21.         }
  22.     };

  23.     var _onerror = function(e){
  24.         if(e.getData().source != window &&
  25.          typeof this.onerror === "function"){
  26.             this.onerror(e.getData().data);
  27.         }
  28.     };
  29.     
  30.     /**
  31.      * Submit new task to the thread
  32.      *
  33.      * @param task It should be a Runnable or a
  34.      * context in Runnable
  35.      * @param isRunnable indicates whether the first parameter "task"
  36.      * is a Runnable
  37.      */
  38.     this.submitTask = function(task, isRunnable){
  39.         if(task == undefined || task == null) return;
  40.         isRunnable = isRunnable || false;
  41.         
  42.         var context, run, callback;
  43.         if(isRunnable){
  44.             context = task.context;
  45.             run = task.run;
  46.             callback = task.callback;
  47.         }else{
  48.             context = task;
  49.             run = runnable.run;
  50.             callback = runnable.callback;
  51.         }

  52.         var buf = new js.lang.StringBuffer();
  53.         buf.append("var thi$ = {");
  54.         buf.append("context:").append(JSON.stringify(context));
  55.         buf.append(",run:").append(run);
  56.         buf.append(",callback:").append(callback);
  57.         buf.append("}");

  58.         var msg = buf.toString();
  59.         //J$VM.System.err.println("Thread post msg: "+msg);
  60.         if(workerType == 0 ){
  61.             worker.postMessage(msg);
  62.         }else{
  63.             worker.postMessage(msg, "*");
  64.         }
  65.     };
  66.     
  67.     /**
  68.      * Start the thread
  69.      */
  70.     this.start = function(){
  71.         this.submitTask(runnable, true);
  72.     };
  73.     
  74.     /**
  75.      * Stop the thread
  76.      */
  77.     this.stop = function(){
  78.         switch(workerType){
  79.         case 0:
  80.             worker.terminate();
  81.             break;
  82.         case 1:
  83.             break;
  84.         }
  85.     };

  86.     var _init = function(Runnable){
  87.         runnable = Runnable || {
  88.             context:{},
  89.             run:function(){
  90.                 thi$.context.result = "Demo is OK";
  91.             },
  92.             callback:ture};
  93.         
  94.         var E = js.util.Event;
  95.         var path = J$VM.env["j$vm_home"]+"/classes/js/util/";

  96.         if(J$VM.isWebWorker){
  97.             worker = new Worker(path+"Worker.js");
  98.             E.attachEvent(worker, "message", 1, this, _onmessage);
  99.             E.attachEvent(worker, "error", 1, this, _onerror);
  100.         }else{
  101.             // iframe for IE ?
  102.             workerType = 1;
  103.             var iframe = document.createElement("iframe");
  104.             iframe.style.cssText = "visibility:hidden;";
  105.             document.body.appendChild(iframe);
  106.             var text = "" +
  107.                 "" +
  108.                 ""+
  109.                 "";
  110.             var doc = iframe.contentDocument, head, script;
  111.             doc.open();
  112.             doc.write(text);
  113.             doc.close();

  114.             head = doc.getElementsByTagName("head")[0];
  115.             text = js.lang.Class.getResource(J$VM.env["j$vm_home"]+"/jsre.js");
  116.             script = doc.createElement("script");
  117.             script.type = "text/javascript";
  118.             script.id = "j$vm";
  119.             script.setAttribute("classpath","");
  120.             script.text = text;
  121.             text = js.lang.Class.getResource(path + "Worker.js");
  122.             script.text += text;
  123.             head.appendChild(script);
  124.             head.removeChild(script);

  125.             worker = iframe.contentWindow;
  126.             E.attachEvent(worker, "message", 0, this, _onmessage);
  127.             E.attachEvent(worker, "error", 0, this, _onerror);            
  128.         }
  129.     };

  130.     _init.$bind(this)(Runnable);
  131.     
  132. }.$extend(js.lang.Object);

4、Worker.js的实现
      
     Worker.js是Web Worker或IFrame Worker需要加载来处理onmessage的,在我的实现中,它并不是象js.lang.Thread一样,是一个通常意义的的类,而是简单的一个javascript文件,只是被管理在js.util的这个目录下:

  1. var isWebWorker = function(){
  2.     try{return (window) ? false : true;} catch (x) {return true;}
  3. }();

  4. var _onmessage = function(e){
  5.     if(isWebWorker){
  6.         eval(e.data);
  7.     }else{
  8.         var _e = e.getData();
  9.         if(_e.source == window) return;

  10.         eval(_e.data);
  11.     }

  12.     if(typeof thi$.run == "function"){
  13.         thi$.run.call(thi$);

  14.         if(thi$.callback){
  15.             if(isWebWorker){
  16.                 postMessage(JSON.stringify(thi$.context));
  17.             }else{
  18.                 window.postMessage(JSON.stringify(thi$.context), "*");
  19.             }
  20.         }
  21.     }
  22. };

  23. if(isWebWorker){
  24.     importScripts("../../../jsre.js");
  25.     onmessage = _onmessage;
  26. }else{
  27.     js.util.Event.attachEvent(window, "message", 0, this, _onmessage);
  28. }
5、结论

    以上代码,已经发布到上,对IE8+, Firefox, Chrome浏览器进行过测试,工作正常。
      
     以后在Javascript里,终于可以象在Java里一样使用多线程计算技术了,当然Web Worker本身的限制是不可避免的,比如不能访问DOM啦。


上一篇:Emacs里几个好用的设置
下一篇:改进Javascript Base64编码传送