【问题标题】:Clean way in GWT/Java to wait for multiple asynchronous events to finishGWT/Java 中等待多个异步事件完成的干净方式
【发布时间】:2010-06-07 20:55:16
【问题描述】:

在继续之前等待多个异步回调函数在 Java 中完成的最佳方法是什么。具体来说,我将 GWT 与 AsyncCallback 一起使用,但我认为这是一个普遍的问题。这就是我现在所拥有的,但肯定有更清洁的方法......

    AjaxLoader.loadApi("books", "0", new Runnable(){
        public void run() {
            bookAPIAvailable = true;
            ready();
        }}, null);
    AjaxLoader.loadApi("search", "1", new Runnable(){
        public void run() {
            searchAPIAvailable = true;
            ready();
        }}, null);


    loginService.login(GWT.getHostPageBaseURL(), new AsyncCallback<LoginInfo>() {
        public void onSuccess(LoginInfo result) {
            appLoaded  = true;
            ready();
        }
    });

private void ready() {
    if(bookAPIAvailable && searchAPIAvailable && appLoaded) {
                // Everything loaded
    }
}

【问题讨论】:

    标签: java gwt google-ajax-api


    【解决方案1】:

    我写了两个类来解决我的项目中的这个问题。基本上,每个单独的回调都会向父级注册。父级等待每个子级回调完成,然后触发它自己的 handleSuccess()。

    客户端代码如下所示:

    
    
    
    public void someGwtClientSideMethod() {
        SomeServiceAsync someService = GWT.create(SomeService.class);
        ParallelCallback fooCallback = new ParallelCallback();
        ParallelCallback barCallback = new ParallelCallback();
        ParentCallback parent = new ParentCallback(fooCallback, barCallback) {
            public void handleSuccess() {
                doSomething(getCallbackData(1), getCallbackData(2));
            }
        };
        someService.foo(fooCallback);
        someService.bar(barCallback);
    }
    

    我在这里写了一篇解释它的帖子:Parallel Asynchronous Calls in GWT。这两个类的实现是从那个帖子链接的(对不起,不能在这里提供链接,因为我是新手用户 - 没有足够的业力来包含多个链接!)。

    【讨论】:

    • @thorp : 能否请您提供一些关于 ParallelCallback 和 ParentCallback 的想法。以上链接不适用于 ParallelCallback 和 ParentCallback。
    • 这是并行的,串行回调呢,其中一个结果输入到另一个。我们通常首先在 onSuccess 内部调用另一个异步调用,我想避免这种情况,至少以这种方式编写。有没有一种干净的方式来串行链接回调?
    • 多线程和同步:JavaScript 解释器是单线程的,所以虽然 GWT 默默地接受了 synchronized 关键字,但它没有真正的效果。同步相关的库方法不可用,包括 Object.wait()、Object.notify() 和 Object.notifyAll()。编译器将忽略 synchronized 关键字,但如果调用了 Object 的相关同步方法,将拒绝编译您的代码。请阅读gwtproject.org/doc/latest/…
    • 我遇到了同样的问题,最后写了一个小库,管理多个同步和异步调用/代码,即使它们相互依赖:github.com/FrankHossfeld/sema4g
    【解决方案2】:

    就像@Epsen 所说,Future 可能是您想要的。不幸的是,我不相信Futures 与 GWT 兼容。 gwt-async-future 项目声称将这个功能带到 GWT,尽管我从未尝试过。可能值得一看。

    【讨论】:

      【解决方案3】:

      我自己也为此苦苦挣扎,并且使用了几种方法——“链”方法变得丑陋(但如果您为每个方法创建类而不是内联类,则可以改进)。

      您自己的版本的变体对我很有效:

      int outstandingCalls = 0;
      {
      outstandingCalls++;
       AjaxLoader.loadApi("books", "0", new Runnable(){
          public void run() {
              ready();
          }}, null);
      
      outstandingCalls++;
      AjaxLoader.loadApi("search", "1", new Runnable(){
          public void run() {
              ready();
          }}, null);
      
      
      outstandingCalls++;
      loginService.login(GWT.getHostPageBaseURL(), new AsyncCallback<LoginInfo>() {
          public void onSuccess(LoginInfo result) {
              ready();
          }
          // Be sure to decrement or otherwise handle the onFailure
      });
      }
      
      private void ready() {
      if (--outstandingCalls > 0) return;
      
      // Everything loaded
      }
      

      我所做的只是为我将要执行的调用次数创建一个计数器,然后每个异步结果调用ready()(请务必在失败方法上也这样做,除非你要做某事不同)

      在 ready 方法中,我将计数器递减,看看是否还有未完成的调用。

      它仍然很丑,但它可以让你根据需要添加调用。

      【讨论】:

        【解决方案4】:

        首先 - 永远不要陷入这种情况。重新设计您的 RPC 服务,以便每个用户流/屏幕最多需要一个 RPC 调用才能工作。在这种情况下,您对服务器进行了三个调用,这只是浪费带宽。延迟只会杀死您的应用。

        如果您不能并且确实需要破解,请使用Timer 定期轮询是否已下载所有数据。您在 assumes login() 方法上方粘贴的代码将是最后完成的 - 这是错误的。它可能是第一个完成的,然后您的应用将处于不确定状态 - 这很难调试。

        【讨论】:

        • 你能解释一下为什么你认为这段代码假定 login() 方法是最后完成的吗?请注意,ready() 方法将被调用 3 次——仅在最后一次调用时评估 true。我一直在成功使用此代码,我认为没有订购问题,但如果我错了,请纠正我...
        • 另外,我同意理想情况下单个 RPC 调用是理想的,但它并不总是可行或实用的。在这种情况下,前两个异步调用甚至不是 RPC 调用,而是加载两个单独的 Google API 的请求,我无法将它们组合起来。
        • 我认为他是在说,如果您正在等待三个 RPC 完成,那么这意味着您正在为服务器创建三个 RPC 服务器。相反,进行一次 RPC 并等待一个响应。 gwt-dispatch (code.google.com/p/gwt-dispatch) 项目使这样的批处理变得更加容易。
        • 不要使用计时器,JavaScript 是单线程的,用户发布的代码很好,只是它忽略了错误(我个人使用一个计数器初始化为等待的响应数,递减每次完成,当它达到 0 时,就绪函数可以继续执行,类似于 JavaScript 信号量:-)。目前在底部的 Sasquatch 和 DeadPassive 的答案是合理的,忽略其余部分。在具有大量解耦组件的复杂应用程序中,并不总是可以组合调用,而无需编写大量难看、无法维护的代码。
        【解决方案5】:

        只是提出一些想法:

        回调使用 HandlerManager 触发一些 GwtEvent。 包含就绪方法的类在 HandlerManager 中注册为回调方法触发的事件的 EventHandler,并保存状态(bookAPIAvailable、searchAPIAvailable、a​​ppLoaded)。

        当一个事件到达时,特定状态会发生变化,我们会检查所有状态是否都符合要求。

        有关使用 GWTEvent、HandlerManager 和 EventHandler 的示例,请参阅http://www.webspin.be/?p=5

        【讨论】:

          【解决方案6】:

          我做了类似于@Sasquatch 的事情,但使用了“CallbackCounter”对象:

          public class CallbackCounter {
              private int outstanding;
              private final Callback<String, String> callback;
              private final String message;
          
              public CallbackCounter(int outstanding, Callback<String, String> callback, String callbackMessage) {
                  this.outstanding = outstanding;
                  this.callback = callback;
                  this.message = callbackMessage;
              }
          
              public void count() {
                  if (--outstanding <= 0) {
                      callback.onSuccess(message);
                  }
              }
          }
          

          然后在我的回调中我只是调用:

          counter.count();
          

          【讨论】:

            【解决方案7】:

            正如 sri 所说,最好的情况是重新设计您的应用程序,使其一次只调用一次后端。这避免了这种情况,并保留了带宽和延迟时间。在网络应用中,这是您最宝贵的资源。

            话虽如此,GWT RPC 模型并不能真正帮助您以这种方式组织事物。我自己也遇到过这个问题。我的解决方案是实现一个计时器。计时器将每 X 秒轮询一次您的结果,当检索到所有预期结果时,您的执行流程可以继续。

            PollTimer extends Timer
            {
                 public PollTimer()
                 {
                      //I've set to poll every half second, but this can be whatever you'd like.
                      //Ideally it will be client side only, so you should be able to make it 
                      //more frequent (within reason) without worrying too much about performance
                      scheduleRepeating(500);
                 }
                 public void run 
                 {
                      //check to see if all your callbacks have been completed
                      if (notFinished)
                          return;

                  //continue with execution flow
                  ...
                 }
            

            }

            调用 RPC,然后实例化一个新的 PollTimer 对象。这应该可以解决问题。

            GWT Emulation 不支持 java.util.concurrent 中的内容。在这种情况下不会帮助你。出于所有意图和目的,您在客户端执行的所有代码都是单线程的。尝试进入这种思维模式。

            【讨论】:

            • 类 java.util.Timer 在 JRE 仿真库中不存在。
            【解决方案8】:

            理想情况下,您希望像其他发帖人所说的那样做,并在一次异步调用中做尽可能多的事情。有时您必须进行一系列单独的调用。方法如下:

            您想要链接异步调用。当最后一个异步完成(登录)时,所有项目都已加载。

                final AsyncCallback<LoginInfo> loginCallback = new AsyncCallback<LoginInfo>() {
                    public void onSuccess(LoginInfo result) {
                        //Everything loaded
                        doSomethingNow();
                    }
                };
                final Runnable searchRunnable = new Runnable() {
                    public void run() {
                        loginService.login(GWT.getHostPageBaseURL(), loginCallback);
                    }
                };
            
                final Runnable booksRunnable = new Runnable() {
                    public void run() {
                        AjaxLoader.loadApi("search", "1", searchRunnable, null);
                    }
                };
            
                //Kick off the chain of events
                AjaxLoader.loadApi("books", "0", booksRunnable, null);
            

            干杯,

            --拉斯

            【讨论】:

            • 链接调用的一个缺点是可能会以串行方式而不是并行方式执行长时间运行的操作。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-10-04
            • 1970-01-01
            • 2021-12-13
            相关资源
            最近更新 更多