39

在继续之前等待多个异步回调函数在 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
    }
}
4

8 回答 8

41

我写了两个类来解决我的项目中的这个问题。基本上,每个单独的回调都会向父级注册。父级等待每个子级回调完成,然后触发它自己的 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。这两个类的实现是从那个帖子链接的(对不起,不能在这里提供链接,因为我是一个新手用户 - 没有足够的业力来包含多个链接!)。

于 2010-11-29T16:14:00.213 回答
5

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

于 2010-06-07T21:09:25.673 回答
4

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

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

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 方法中,我递减计数器并查看是否还有未完成的调用。

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

于 2012-03-14T03:16:41.553 回答
2

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

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

于 2010-06-07T21:07:22.730 回答
1

只是折腾一些想法:

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

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

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

于 2010-06-12T22:02:05.917 回答
1

我做了类似于@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();
于 2012-06-22T09:53:14.413 回答
0

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

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

    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);

干杯,

——拉斯

于 2010-07-08T19:31:34.220 回答
0

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

话虽如此,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 中的内容。在这种情况下不会帮助你。出于所有意图和目的,您在客户端执行的所有代码都是单线程的。试着进入那种思维定势。

于 2010-06-07T22:52:39.327 回答