GWT / JSNI - “DataCloneError - 无法克隆对象” - 我该如何调试?

时间:2014-02-03 21:22:39

标签: gwt jsni

我试图通过JSNI调用parallels.js。 Parallels为Web工作者提供了一个很好的API,我编写了一些轻量级的包装代码,它为GWT的工作者提供了比Elemental更方便的界面。但是我收到了一个让我难过的错误:

com.google.gwt.core.client.JavaScriptException:(DataCloneError)@ io.mywrapper.workers.Parallel :: runParallel([Ljava / lang / String; Lcom / google / gwt / core / client / JavaScriptObject; Lcom / google / gwt / core / client / JavaScriptObject;)([Java对象:[Ljava.lang.String; @ 1922352522,JavaScript对象(3006),JavaScript对象(3008)]):无法克隆对象。

这来自托管模式:

com.google.gwt.dev.shell.BrowserChannelServer.invokeJavascript(BrowserChannelServer.java:249)com.google上com.google.gwt.dev.shell.ModuleSpaceOOPHM.doInvoke(ModuleSpaceOOPHM.java:136)。 gwt.dev.shell.ModuleSpace.invokeNative(ModuleSpace.java:571)位于com.google.gwt.dev.shell.JavaScriptHost的com.google.gwt.dev.shell.ModuleSpace.invokeNativeVoid(ModuleSpace.java:299)。 invoNativeVoid(JavaScriptHost.java:107)at io.mywrapper.workers.Parallel.runParallel(Parallel.java)

这是我的代码:

创建工作人员的客户端调用示例:

    Workers.spawnWorker(new String[]{"hello"}, new Worker() {
        @Override
        public String[] work(String[] data) {
            return data;
        }

        @Override
        public void done(String[] data) {
            int i = data.length;
        }
    });

提供通用界面的API:

public class Workers {

    public static void spawnWorker(String[] data, Worker worker) {
        Parallel.runParallel(data, workFunction(worker), callbackFunction(worker));
    }

    /**
     * Create a reference to the work function.
     */
    public static native JavaScriptObject workFunction(Worker worker) /*-{
        return worker == null ? null : $entry(function(x) {
            worker.@io.mywrapper.workers.Worker::work([Ljava/lang/String;)(x);
        });
    }-*/;

    /**
     * Create a reference to the done function.
     */
    public static native JavaScriptObject callbackFunction(Worker worker) /*-{
        return worker == null ? null : $entry(function(x) {
            worker.@io.mywrapper.workers.Worker::done([Ljava/lang/String;)(x);
        });
    }-*/;        
}

工人:

public interface Worker extends Serializable {

    /**
     * Called to perform the work.
     * @param data
     * @return
     */
    public String[] work(String[] data);

    /**
     * Called with the result of the work.
     * @param data
     */
    public void done(String[] data);

}

最后是Parallels包装器:

public class Parallel {

    /**
     * @param data Data to be passed to the function
     * @param work Function to perform the work, given the data
     * @param callback Function to be called with result
     * @return
     */
    public static native void runParallel(String[] data, JavaScriptObject work, JavaScriptObject callback) /*-{
        var p =  new $wnd.Parallel(data);
        p.spawn(work).then(callback);
    }-*/;
}

造成这种情况的原因是什么?

JSNI文档说,关于数组:

opaque value that can only be passed back into Java code

这很简洁,但最终我的数组被传回Java代码,所以我认为这些都没问题。

编辑 - 好的,不好的假设。尽管表面上只是传递回Java代码,但这些数组导致错误(这很奇怪,因为DataCloneError上的googleability很少。)将它们更改为String工作;但是,String不足以满足我的需求。看起来对象面临与数组相同的问题;我在另一个StackOverflow线程中看到了Thomas对JSArrayUtils的引用,但我无法弄清楚如何用一个字符串数组来调用它(它想要一个JavaScriptObjects数组作为非原始类型的输入,这对我没有好处。)这有什么好的方法吗?

编辑2 - 更改为在使用String []的任何地方使用JSArrayString。新问题;这次没有堆栈跟踪,但在控制台中我收到错误:未捕获的ReferenceError:__gwt_makeJavaInvoke未定义。当我点击开发人员工具中生成的脚本的url时,我得到了这个片段:

self.onmessage = function(e) {self.postMessage((function (){
    try {
      return __gwt_makeJavaInvoke(3)(null, 65626, jsFunction, this, arguments);
    }
     catch (e) {
      throw e;
    }
  })(e.data))}

我看到_gwt_makeJavaInvoke是JSNI类的一部分;那为什么不能找到呢?

2 个答案:

答案 0 :(得分:3)

您可以在此处找到GWT和WebWorkers的工作示例:https://github.com/tomekziel/gwtwwlinker/

这是一项初步工作,但使用此模式,我能够使用AutoBeanFactory提供的序列化将GWT对象传入和传出webworker。

答案 1 :(得分:1)

如果从不使用开发模式,当前是安全的,假装Java String[]是一个包含字符串的JS数组。这将在开发模式中中断,因为数组必须在Java中可用并且字符串被特殊处理,并且如果编译器以不同方式优化数组,将来可能会中断。

将来可能出错的情况:

Java数组和JavaScript数组的语义不同 - 无法调整Java数组的大小,并根据组件类型(数组中的数据)使用特定值进行初始化。由于您正在编写Java代码,因此编译器可以根据有关如何创建和使用该数组的详细信息进行假设,这些数据可能被JS代码打破,而JS代码并不知道永远不会修改数组。

一些原始类型的数组可以在JavaScript中优化为TypedArrays,在分配方面调整大小和Java行为方面更接近Java语义。这也会提升性能,但可能会破坏int[]double[]等的使用。


相反,您应该将数据复制到JsArrayString,或者只使用js数组来保存数据而不是来回移动,具体取决于您的使用情况。可以调整各种JsArray类型的大小,并且已经存在于JS之外可以理解和使用的JavaScript对象中。


回复编辑2:

猜测,parallel.js脚本试图从另一个范围运行你的代码,例如在webworker中(这是代码的重点,右边)你的GWT代码不存在。因此,它无法调用makeJavaInvoke,这是桥接器进入开发模式(与编译的JS会有不同的故障)。根据{{​​3}},有一些特定的要求,传递的回调必须满足传递给spawnthen - 您的匿名函数肯定不符合它们,并且可能无法实现维护java语义。

在我深入了解之前,请查看我之前做过的回答,解决了webworkers和gwt / java的基本问题:http://adambom.github.io/parallel.js/

如前所述,WebWorkers实际上是新流程,没有与原始流程共享的代码或共享状态。 Parallel.js代码试图通过一些技巧来解决这个问题 - 共享状态以传递给原始Parallel构造函数的内容的形式提供,但是你尝试传递' java'的实例对象和它们的调用方法。这些Java实例具有自己的状态,并且可能可以通过Worker实例中的字段链接回Java应用程序的其余部分。如果我正在实现Worker并做一些引用其他数据的东西,那么我会看到更多奇怪的失败。

因此,您传入的函数必须完全独立 - 它们不得以任何方式引用外部代码,因为此功能无法传递给Webworker,或者几个网络工作者,每个人都不知道彼此的存在。例如,请参阅https://stackoverflow.com/a/11376059/860630

  

这是不可能的,因为它会

     
      
  • 需要跨员工的共享状态
  •   
  • 要求我们传输所有范围变量(我认为甚至没有可能阅读可用范围)
  •   
     

唯一可能的是缓存变量,但这些已经可以在函数本身中用spawn()定义,并且在地图中没有任何意义(因为没有共享状态) )。

实际上并不熟悉parallel.js是如何实现的(到目前为止,所有这些答案都是阅读文档和快速谷歌搜索" parallel.js共享状态"以及体验过WebWorkers for大约一天左右,并且决定我现在的问题还不值得麻烦,我猜我then不受限制,你可以随心所欲地传递它,但是spawnmapreduce必须以这样一种方式编写,即JS可以传递给新的JS进程并完全独立于那里。

这个可能可以在编译时从普通的Java代码中获得,只要你只有一个Worker的实现,并且impl永远不会使用除直接传入的状态之外的状态。在这种情况下,编译器应该将您的方法重写为静态,以便在此上下文中使用它们是安全的。但是,这并不是一个非常有用的库,因为它似乎正在努力实现。考虑到这一点,您可以将您的工作人员代码保存在JSNI中,以确保您遵循parallel.js规则。

最后,针对正常的GWT规则,避免$entry用于您希望在其他上下文中发生的调用,因为这些工作者无法访问正常的异常处理和$ entry启用的调度。

(最后,如果你非常谨慎地编写Worker实现并编写一个Gene,以非常具体的方式调用每个worker实现来确保com.google.gwt.dev.jjs.impl.MakeCallsStaticcom.google.gwt.dev.jjs.impl.Pruner一旦他们被重写为JS函数,就可以正确地行动来淘汰那些实例方法中的this。我认为最简单的方法就是在其中发出JSNI生成器本身,调用用真实Java编写的静态方法,并从该静态方法调用为生成繁重的特定实例方法,等等。)