Чистый способ в 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
это, вероятно, то, что вы хотите. К сожалению я не верю Future
S являются 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