Quelle est la meilleure façon d'attendre que plusieurs fonctions de rappel asynchrones se terminent en Java avant de continuer. Plus précisément, j'utilise GWT avec AsyncCallback, mais je pense que c'est un problème générique. Voici ce que j'ai maintenant, mais il y a sûrement un moyen plus propre ...
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
}
}
J'ai écrit deux classes qui résolvent ce problème sur mon projet. Fondamentalement, chaque rappel individuel s'inscrit auprès d'un parent. Le parent attend la fin de chaque rappel enfant, puis déclenche son propre handleSuccess ().
Le code client ressemble à ceci:
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);
}
</code>
J'ai écrit un article l'expliquant ici: Appels asynchrones parallèles dans GWT . L'implémentation de ces deux classes est liée à partir de ce post (désolé, je ne peux pas donner de liens ici parce que je suis un utilisateur débutant - pas assez de karma pour inclure plus d'un lien!).
Comme le dit @Epsen, Future
est probablement ce que vous voulez. Malheureusement, je ne pense pas que les Future
soient compatibles avec GWT. Le projet gwt-async-future prétend apporter cette fonctionnalité à GWT, même si je ne l'ai jamais essayé. Cela vaut peut-être le coup d'œil.
J'ai moi-même eu des problèmes avec cela, et j'ai utilisé plusieurs méthodes - la "chaîne" devient laide (mais peut être améliorée si vous créez des classes au lieu de classes en ligne pour chaque méthode).
Une variante de votre propre version fonctionne bien pour moi:
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
}
Tout ce que j'ai fait, c'est créer un compteur pour le nombre d'appels que je vais faire, puis chaque résultat asynchrone appelle ready()
(assurez-vous de le faire également sur les méthodes d'échec, sauf si vous allez le faire quelque chose de différent)
Dans la méthode ready, je décrémente le compteur et vois s'il y a encore des appels en attente.
C'est toujours moche, mais cela vous permet d'ajouter des appels au besoin.
D'abord et avant tout - ne vous mettez jamais dans une telle situation. Reconcevoir vos services RPC de telle sorte que chaque flux/écran utilisateur nécessite au plus un seul appel RPC pour fonctionner. Dans ce cas, vous passez trois appels au serveur, et c'est juste un gaspillage de bande passante. La latence ne fera que tuer votre application.
Si vous ne pouvez pas et avez vraiment besoin d'un hack, utilisez un Timer pour interroger périodiquement si toutes les données ont été téléchargées. Le code que vous avez collé ci-dessus suppose que la méthode login () sera la dernière à terminer - ce qui est faux. C'est peut-être le premier à terminer, puis votre application sera dans un état indéterminé - ce qui est très difficile à déboguer.
J'ai fait quelque chose de similaire à @Sasquatch, mais à la place en utilisant un objet "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);
}
}
}
Ensuite, dans mon rappel, j'appelle simplement:
counter.count();
Jeter quelques idées:
Les rappels déclenchent certains GwtEvent à l'aide de HandlerManager. La classe contenant les méthodes prêtes est enregistrée auprès du HandlerManager en tant que gestionnaire d'événements pour les événements déclenchés par les méthodes de rappel et contient l'état (bookAPIAvailable, searchAPIAvailable, appLoaded).
Lorsqu'un événement arrive, cet état spécifique est modifié, et nous vérifions si tous les états sont comme souhaité.
Pour un exemple utilisant GWTEvent, HandlerManager et EventHandler, voir http://www.webspin.be/?p=5
Idéalement, vous voulez faire ce que d'autres affiches ont déclaré et faire autant que vous le pouvez en un seul appel asynchrone. Parfois, vous devez faire un tas d'appels séparés. Voici comment:
Vous souhaitez chaîner les appels asynchrones. Une fois la dernière async terminée (connexion), tous les éléments sont chargés.
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);
À votre santé,
--Russ
Le meilleur scénario, comme l'a dit Sri, est de repenser votre application pour n'appeler le backend qu'une seule fois à la fois. Cela évite ce type de scénario et préserve la bande passante et le temps de latence. Dans une application Web, c'est votre ressource la plus précieuse.
Cela dit, le modèle GWT RPC ne vous aide pas vraiment à organiser les choses de cette manière. J'ai rencontré ce problème moi-même. Ma solution était d'implémenter une minuterie. La minuterie interrogera vos résultats toutes les X secondes, et lorsque tous les résultats attendus seront récupérés, votre flux d'exécution pourra continuer.
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
...
}
</code>
}
Appelez votre RPC, puis instanciez un nouvel objet PollTimer. Cela devrait faire l'affaire.
Le contenu de Java.util.concurrent n'est pas pris en charge par l'émulation GWT. Ne vous aidera pas dans ce cas. À toutes fins utiles, tout le code que vous faites du côté client est à thread unique. Essayez d'entrer dans cet état d'esprit.