What is the best way to wait for multiple asynchronous callback functions to finish in Java before continuing. Specifically I\'m using GWT with AsyncCallback, but I think this i
I did something similar to @Sasquatch, but instead making use of a "CallbackCounter" object:
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);
}
}
}
Then in my callback I just call:
counter.count();
I've struggled with this myself, and I've used several methods- the 'chain' one just gets ugly (but can be improved if you create classes instead of inline classes for each method).
A variant of your own version works well for me:
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
}
All I did was create a counter for the number of calls I'm going to do, then each async result calls ready()
(be sure to do this on the failure methods too, unless you're going to do something different)
In the ready method, I decrement the counter and see if there are still outstanding calls.
It's still ugly, but it lets you add calls as needed.
First and foremost - don't ever get into such a situation. Redesign your RPC services such that every user flow/screen requires at most a single RPC call to work. In this case, you are making three calls to the server, and its just a waste of bandwidth. The latency will just kill your app.
If you can't and really need a hack, use a Timer to periodically poll if all data has downloaded. The code you pasted above assumes login() method will be the last to finish - which is wrong. Its may be the first to finish, and then your app will be in an indeterminate state - which is very difficult to debug.
Like @Epsen says, Future
is probably what you want. Unfortunately, I don't believe Future
s are GWT-compatible. The gwt-async-future project claims to bring this functionality to GWT, though I've never tried it. It may be worth a look.
I wrote two classes that solve this problem on my project. Basically, each individual callback registers with a parent. The parent waits for each child callback to complete, then fires off it's own handleSuccess().
The client code looks like this:
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);
}
I wrote a post explaining it here: Parallel Asynchronous Calls in GWT. The implementation for these two classes is linked from that post (sorry, can't give links here because I'm a newbie user - not enough karma to include more than one link!).
Best case scenario, as sri said, is to redesign your app to only call the backend once at a time. This avoids this kind of scenario, and preserves bandwidth and latency time. In a web app, this is your most precious resource.
Having said that the GWT RPC model doesn't really help you to organize things in this manner. I've run into this problem myself. My solution was to implement a timer. The timer will poll your results every X seconds, and when all your expected results are retrieved, your execution flow can continue.
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
...
}
}
Make your calls to your RPC, then instantiate a new PollTimer object. That should do the trick.
The stuff in java.util.concurrent is not supported by GWT Emulation. Wont help you in this case. For all intents and purposes, all of the code you do on the client side is single threaded. Try to get into that mind set.