11.4 线程阻塞问题

11.4.1 问题描述

Firefox、老版 Chrome 浏览器有一个很独特的问题：js 主线程不允许被插件长时间阻塞。
例如，你在 js 中执行树列表的转换输出函数：

    var filename=AF.func("OpenExportDialog", "");

或者执行硕正报表同样功能的函数：

    var filename=AF.func("callfunc", "103 \r\n type=xlsx");

这些函数将弹出一个转换输出选项的模式对话框，在几秒钟后，Firefox极有可能出现如下警告界面, 继而卡死、崩溃：
老版本 Chrome 浏览器则可能会在右上角出现如下提示, 但不会崩溃：
我们知道，模式对话框打开后，线程是阻塞的，一直到对话框关闭才释放, 而 Firefox 对主线程的控制权极为敏感，不允许插件长时间阻塞主线程，这就是问题的主因。
如果不是 js 调用，而是用鼠标点击插件自己的菜单或工具条、执行相应功能，它是不会弹出这个警告的。

IE浏览器、新版 Chrome (ppapi) 不存在这个问题.

11.4.2 解决方案

为了解决这个问题，硕正插件从1.0.94.0版开始，增加了一个名为 DeclareAsynch 的新函数，采用多线程方式，将打开对话框的过程安置到新的线程中运行，从而绕开的这个问题。
函数 DeclareAsynch 使用也很简单，只要在函数上方执行一遍即可，例如您可以这样写：

    AF.func("DeclareAsynch", "");
    AF.func("OpenExportDialog", "");

    AF.func("DeclareAsynch", "");
    AF.func("callfunc", "103 \r\n type=xlsx");

紧挨在 DeclareAsynch 下方的插件函数将被安排在新的线程中运行，并且不等对话框关闭，函数将立即返回，不阻塞主线程。
DeclareAsynch 函数的详细文档在全局函数中，请自行参考。

11.4.3 返回值

用此方案，也导致了一个新的问题：如何获取函数的返回值?

    var filename=AF.func("OpenExportDialog", "");  //返回 filename, 文件名

如果先执行 DeclareAsynch，你会发现返回的 filename 永远是空串!
因为必须要等对话框关闭才能获取确切的文件名，所以，我们只能通过消息事件来传递这个返回值： 在模式对话框关闭后、新线程终止前，插件将触发名为 UserEvent 的事件，该事件的 P4 参数就是返回值 - 文件名。该事件的P1, p2, p3参数由 DeclareAsynch 函数指定，举例如下：

    function onMyMenu()
    {
     AF.func("DeclareAsynch", "p1=myFunc");
     AF.func("callfunc", "103 \r\n type=xlsx");
    }

    //硕正事件入口
    function OnEvent(id, Event, p1, p2, p3, p4)
    {
     if(Event == "UserEvent" && p1 =="myFunc") {
       //这就是线程终止时触发的
       //p4就是 callfunc 的返回值
       alert(p4);
     }
    }

分析上面的 js 可知，这本质上就是常规的异步解决方案，异步往往是和多线程、消息事件关联的.

11.4.4 补充说明

1.DeclareAsynch 函数本身和 Firefox、Chrome 无关，在其它浏览器下也一样能使用，所以按上述方案处理，也是能兼容其它浏览器的;
2.建议 DeclareAsynch 只用于会弹出对话框的场合，因为新线程没有独立的消息循环, 用在其它地方可能会出现无法预料的错误;
3.在函数的下方，不建议书写其它针对插件的函数，因为多线程运行，会有意想不到的后果的.