Чистый способ в GWT / Java ждать завершения нескольких асинхронных событий - PullRequest
39 голосов
/ 08 июня 2010

Каков наилучший способ дождаться завершения нескольких функций асинхронного обратного вызова в 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
    }
}

Ответы [ 8 ]

41 голосов
/ 29 ноября 2010

Я написал два класса, которые решают эту проблему в моем проекте. По сути, каждый отдельный обратный вызов регистрируется с родителем. Родитель ожидает завершения каждого дочернего обратного вызова, а затем запускает свой собственный handleSuccess ().

Код клиента выглядит следующим образом:

</p>

<pre><code>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);
}

Я написал пост, объясняющий это здесь: Параллельные асинхронные вызовы в GWT . Реализация этих двух классов связана с этим постом (извините, здесь я не могу дать ссылки, потому что я новичок - недостаточно кармы, чтобы включить более одной ссылки!)

5 голосов
/ 08 июня 2010

Как говорит @Epsen, Future, вероятно, то, что вы хотите. К сожалению, я не верю, что Future являются GWT-совместимыми. Проект gwt-async-future претендует на использование этой функциональности в GWT, хотя я никогда не пробовал. Это может стоить посмотреть.

4 голосов
/ 14 марта 2012

Я сам с этим боролся, и я использовал несколько методов - «цепной» один становится просто уродливым (но его можно улучшить, если вы создадите классы вместо встроенных классов для каждого метода).

Мне подходит вариант вашей собственной версии:

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 я уменьшаю счетчик и вижу, есть ли еще ожидающие вызовы.

Это все еще уродливо, но позволяет добавлять звонки по мере необходимости.

2 голосов
/ 08 июня 2010

Прежде всего - никогда не попадите в такую ​​ситуацию. Перепроектируйте свои службы RPC таким образом, чтобы для каждого потока / экрана пользователя требовалось не более одного вызова RPC. В этом случае вы делаете три вызова на сервер, и это просто трата пропускной способности. Задержка просто убьет ваше приложение.

Если вы не можете и действительно нуждаетесь во взломе, используйте Таймер для периодического опроса, если все данные загружены. Код, который вы вставили выше , предполагает, что метод login () будет последним завершать - что неверно. Это может быть первым, чтобы закончить, и тогда ваше приложение будет в неопределенном состоянии - что очень трудно отладить.

1 голос
/ 22 июня 2012

Я сделал что-то похожее на @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();
1 голос
/ 13 июня 2010

Просто подбрасываю некоторые идеи:

Обратные вызовы запускают некоторое GwtEvent, используя HandlerManager. Класс, содержащий готовые методы, зарегистрирован в HandlerManager как EventHandler для событий, инициируемых методами обратного вызова, и содержит состояние (bookAPIAvailable, searchAPIAvailable, appLoaded).

Когда приходит событие, это конкретное состояние изменяется, и мы проверяем, все ли состояния соответствуют желаемым.

Пример использования GWTEvent, HandlerManager и EventHandler см. http://www.webspin.be/?p=5

0 голосов
/ 08 июля 2010

В идеале вы хотите сделать так, как заявили другие авторы, и сделать как можно больше за один асинхронный вызов. Иногда вам нужно сделать несколько отдельных звонков. Вот как это сделать:

Вы хотите связать асинхронные вызовы. Когда последний асинхронный процесс завершается (вход в систему), все элементы загружаются.

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

Приветствия

- Russ

0 голосов
/ 08 июня 2010

Лучший сценарий, как сказал Шри, - это перепроектировать ваше приложение, чтобы вызывать бэкэнд только один раз. Это позволяет избежать такого рода сценариев и сохраняет пропускную способность и время ожидания. В веб-приложении это ваш самый ценный ресурс.

Сказав, что модель GWT RPC на самом деле не помогает вам организовать вещи таким образом. Я сам столкнулся с этой проблемой. Моим решением было реализовать таймер. Таймер будет опрашивать ваши результаты каждые X секунд, и когда все ваши ожидаемые результаты будут получены, ваш поток выполнения может продолжаться.

<pre> 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;</p> <pre><code> //continue with execution flow ... }

}

Выполните вызовы вашего RPC, а затем создайте новый объект PollTimer. Это должно сработать.

Материал в java.util.concurrent не поддерживается эмуляцией GWT. Не поможет вам в этом случае. В любом случае весь код, выполняемый на стороне клиента, является однопоточным. Попробуйте войти в это мышление.

...