Чистый способ в GWT/Java ждать завершения нескольких асинхронных событий

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

Я написал два класса, которые решают эту проблему в моем проекте. По сути, каждый отдельный обратный вызов регистрируется с родителем. Родитель ожидает завершения каждого обратного вызова, а затем запускает свой собственный 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);
}

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

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

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

Вариант вашей собственной версии хорошо работает для меня:

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

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

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

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

Я сделал что-то похожее на @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();

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

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

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

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

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

Сказав, что модель 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. Это должно делать свое дело.

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

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

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

    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

Другие вопросы по тегам