Чистый способ в 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
    }
}

person gerdemb    schedule 07.06.2010    source источник


Ответы (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. Реализация для этих двух классов связана с этим сообщением (извините, я не могу дать ссылки здесь, потому что я новичок - недостаточно кармы, чтобы включить более одной ссылки!).

person throp    schedule 29.11.2010
comment
@thorp: Не могли бы вы дать некоторое представление о ParallelCallback и ParentCallback. Приведенные выше ссылки не работают для ParallelCallback и ParentCallback. - person Dhruva; 10.12.2012
comment
Это параллельно, как насчет последовательных обратных вызовов, когда один результат вводится в другой. Обычно мы вызываем другой асинхронный вызов внутри onSuccess of first, я хочу избежать этого, по крайней мере, писать таким образом. Есть ли чистый способ последовательного объединения обратных вызовов? - person Naveen; 10.09.2014
comment
Многопоточность и синхронизация: интерпретаторы JavaScript являются однопоточными, поэтому, хотя GWT молча принимает ключевое слово synchronized, это не имеет реального эффекта. Библиотечные методы, связанные с синхронизацией, недоступны, включая Object.wait(), Object.notify() и Object.notifyAll(). Компилятор проигнорирует ключевое слово synchronized, но откажется компилировать ваш код, если будут вызваны связанные с объектом методы синхронизации. Прочтите gwtproject.org/doc/latest/ - person idmitriev; 09.11.2015
comment
У меня была та же проблема, и в итоге я написал небольшую библиотеку, которая управляла несколькими синхронными и асинхронными вызовами/кодом, даже если они зависят друг от друга: github.com/FrankHossfeld/sema4g - person El Hoss; 07.10.2017

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

person Jason Hall    schedule 07.06.2010

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

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

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() (обязательно сделайте это и для методов сбоя, если вы не собираетесь делать что-то другое)

В готовом методе я уменьшаю значение счетчика и смотрю, есть ли еще невыполненные вызовы.

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

person Sasquatch    schedule 14.03.2012

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

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

person Sripathi Krishnan    schedule 07.06.2010
comment
Можете ли вы объяснить, почему вы думаете, что этот код предполагает, что метод login() будет последним. Обратите внимание, что метод ready() будет вызываться три раза — только при последнем вызове будет оцениваться значение true. Я успешно использую этот код, и я не думаю, что есть проблема с заказом, но поправьте меня, если я ошибаюсь... - person gerdemb; 08.06.2010
comment
Кроме того, я согласен с тем, что в идеале один вызов RPC был бы идеален, но это не всегда возможно или практично. В этом случае первые два асинхронных вызова — это даже не вызовы RPC, а запросы на загрузку двух отдельных API Google, и я никак не могу их объединить. - person gerdemb; 08.06.2010
comment
Я думаю, он имел в виду, что если вы ждете завершения трех RPC, то это означает, что вы делаете три RPC-сервера для сервера. Вместо этого сделайте один RPC и дождитесь одного ответа. gwt-dispatch (code.google.com/p/gwt-dispatch) project значительно упрощает пакетную обработку. - person Jason Hall; 08.06.2010
comment
Не используйте таймер, JavaScript является однопоточным, то, что пользователь отправил для кода, в порядке, за исключением того, что он игнорирует ошибки (лично я использую счетчик, инициализированный количеством ответов для ожидания, уменьшающийся при каждом завершении и когда он достигает 0 , то готовая функция может продолжить работу, своего рода семафор JavaScript :-). Ответы Sasquatch и DeadPassive, которые в настоящее время находятся внизу, разумны, остальные игнорируются. В сложном приложении с большим количеством разрозненных компонентов не всегда возможно объединить вызовы, не написав при этом много уродливого, невозможного в сопровождении кода. - person Tony BenBrahim; 15.09.2015

Просто подкинул пару идей:

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

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

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

person Davy Meers    schedule 12.06.2010

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

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

Сказав, что модель 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. Не поможет вам в этом случае. Во всех смыслах и целях весь код, который вы делаете на стороне клиента, является однопоточным. Попытайтесь войти в этот образ мыслей.

person Enmanuel Rivera    schedule 07.06.2010
comment
класс java.util.Timer отсутствует в библиотеке эмуляции JRE. - person Alex; 28.08.2012

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

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

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

Ваше здоровье,

--Русь

person Russ Sherk    schedule 08.07.2010
comment
Недостатком объединения вызовов в цепочку является то, что потенциально длительные операции выполняются последовательно, а не параллельно. - person gb96; 30.05.2013