Clean way in GWT/Java to wait for multiple asynchronous events to finish
Asked Answered
S

8

39

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 is a generic problem. Here's what I have now, but surely there is cleaner way...

    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
    }
}
Sarraute answered 7/6, 2010 at 20:55 Comment(0)
H
42

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!).

Halting answered 29/11, 2010 at 16:14 Comment(4)
@thorp :Can u please give some idea about ParallelCallback & ParentCallback .The above links are not working for ParallelCallback &ParentCallback.Rector
This is parallel, what about serial callbacks, where one result is input to other. We generally call another async call inside onSuccess of first, I want to avoid that, atleast writing in that fashion. Is there a clean way of chaining callbacks serially.?Boucher
Multithreading and Synchronization: JavaScript interpreters are single-threaded, so while GWT silently accepts the synchronized keyword, it has no real effect. Synchronization-related library methods are not available, including Object.wait(), Object.notify(), and Object.notifyAll(). The compiler will ignore the synchronized keyword but will refuse to compile your code if the Object’s related synchronization methods are invoked. Please read gwtproject.org/doc/latest/…Meneses
I had the same problem and ended up in writing a small lib, that managed several synchronous and asynchronous calls/code, even if they depend on each other: github.com/FrankHossfeld/sema4gCorot
C
5

Like @Epsen says, Future is probably what you want. Unfortunately, I don't believe Futures 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.

Chair answered 7/6, 2010 at 21:9 Comment(0)
H
4

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.

Hammon answered 14/3, 2012 at 3:16 Comment(0)
R
2

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.

Romp answered 7/6, 2010 at 21:7 Comment(4)
Can you explain why you think this code assumes the login() method will be the last to finish. Please note that the ready() method will be called three times--only evaluating true on the last call. I've been using this code successfully and I don't think there is ordering problem, but correct me if I'm wrong...Sarraute
Also, I agree ideally a single RPC call would be ideal, but it's not always possible or practical. In this case, the first two asynchronous calls are not even RPC calls, but are requests to load two separate Google APIs and there is no way that I can combine them.Sarraute
I think he was saying that if you're waiting for three RPCs to finish, then that means you're making three RPC servers to the server. Instead, make one RPC and wait for one response. The gwt-dispatch (code.google.com/p/gwt-dispatch) project makes batching like this much easier.Chair
Do not use a timer, JavaScript is single threaded, what the user posted for code is fine, except that it ignores errors (I personally use a counter initialized with number of responses to wait for, decrement with each completion, and when it hits 0, then the ready function can proceed, kind of a JavaScript semaphore :-). The answers by Sasquatch and DeadPassive, currently at the bottom, are reasonable, ignore the rest. In a complex application with a lot of decoupled components, it is not always possible to combine calls, without writing a lot of ugly, impossible to maintain code.Herniotomy
P
1

Just tossing up some ideas:

The callbacks fire some GwtEvent using the HandlerManager. The class containing the ready methods is registered with the HandlerManager as an EventHandler for the events fired by the callback methods, and holds the state (bookAPIAvailable, searchAPIAvailable, appLoaded).

When a event arrives that specific state is changed, and we check if all the states are as desired.

For an example using the GWTEvent, HandlerManager and EventHandler, see http://www.webspin.be/?p=5

Philippopolis answered 12/6, 2010 at 22:2 Comment(0)
E
1

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();
Eneidaenema answered 22/6, 2012 at 9:53 Comment(0)
E
0

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.

Evora answered 7/6, 2010 at 22:52 Comment(1)
class java.util.Timer is not present in JRE emulation library.Orchestra
B
0

Ideally, you want to do as other posters have stated and do as much as you can in a single async call. Sometimes you have to do a bunch of separate calls. Here's how:

You want to chain the async calls. When the last async completes (login), all the items are loaded.

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

Cheers,

--Russ

Berbera answered 8/7, 2010 at 19:31 Comment(1)
One downside to chaining calls is that potentially long-running operations are performed in series instead of in parallel.Syman

© 2022 - 2024 — McMap. All rights reserved.