3

I've started working on an existing project (an sdk) at work, and the code base uses listeners like there's no tomorrow. Pretty much every second method takes some sort of listener argument and I have yet to find an occurrence where the listener is not the calling class or an anonymous class declared when the method is called.

My question is: when/why is it appropriate to use a Listener over Future<> in this way?

// Util methods
static void postSuccess(Listener listener, Object object) {
    Util.runOnUI(() -> { listener.success(object); });
}
static void postError(Listener, Exception ex) {
    Util.runOnUI(() -> { listener.failure(ex); });
}

void foo ( Bar bar, Listener listener ) {
    executor.submit(() -> {
        try {
            // work_on() is a potentially long running function
            Result res = work_on( bar );
            Util.postSuccess(listener, res)
         }
         catch (Exception ex) {
            Util.postError(listener, ex);
         }
     }
}

In the above code the caller has no control over when the listener's method is invoked. The following code would allow the caller to decide whether to wait or check it later and generally seems to provide more control:

Future<Result> foo ( Bar bar ) {
    return executor.submit(() -> { return workOn( bar ) };
}

Apologies if I'm missing something obvious, I'm only out of college three months, but this listener code smells bad to me.

5
  • The first example is still synchronous right ? (as the second will return immediatly)
    – Spotted
    Sep 30 '16 at 11:07
  • Sorry, I think I simplified my example a little too much. The codebase in question uses Util.postSuccess(Listener<T>, T object) and Util.postFailure(Listener<T> listener, T object) which run the corresponding listener method on another thread.
    – Alex Meuer
    Sep 30 '16 at 11:59
  • 1
    Can you update your example accordingly ? It will help others to better understand your question.
    – Spotted
    Sep 30 '16 at 12:06
  • @Spotted Should be easier to understand now; Added relevant functions and was more explicit regarding threads. I definitely left out too much detail to begin with.
    – Alex Meuer
    Sep 30 '16 at 12:23
  • 1
    What does Util.runOnUI() do? To me it implies a Swing application, which requires a callback pattern to avoid blocking the event dispatch thread. With a Future, you either risk blocking by calling get(), or waste time calling isDone().
    – kdgregory
    Sep 30 '16 at 15:07
7

With both alternatives, you can achieve the same functionality: running some long piece of code without blocking the caller and provide a mean to give the result later.

But from my own experience, I always foster Future over callback for the following reasons:

  • Program flow is much harder to understand with callbacks than with Future. The flow is like "jumping" making it also hard to debug.
  • Callbacks denature the method's signature. When you look at void foo(Bar bar, Listener listener), first: it returns void although we would have expected this method to return something in a synchronous context. Second: the parameters list is polluted with a Listener that has nothing to do with the domain (= visual noise). On the other hand, with a Future the intent is clearer: I need a Bar in order to deliver you a Result, but not directly, only in some future.
  • You can end up in the callback hell. With Future your code stays more composable and evolutive. By the way, Java 8 is also shipped with a whole new API called CompletableFuture that makes the usage of asynchronism more expressive and relies on Future.
  • With callbacks, you enforce the thread in which the listener is called (in your case the UI thread). But what if the user doesn't want the callback to be run in the UI thread ? It's too late because this dependency has been hardcoded in your code and the caller is stuck. With Future the caller is free to choose the thread he wants to handle the result in.
  • Callbacks can create a lot of garbage threads if done carelessly because each listener has to be invoked in another thread (this is, however, not the case in your example since you always use the UI thread). With Future only one thread is involved.
3
  • Very informative, thank you. I'll be making the case at our next meeting as to why forcing the use of callbacks on clients of our sdk might not be such a good plan.
    – Alex Meuer
    Sep 30 '16 at 12:53
  • @AlexMeuer I've updated my answer with more information. I'll notify you if more arguments come in mind in the future. :-)
    – Spotted
    Sep 30 '16 at 13:07
  • Can you please give code example of Future which achieves the same functionality as Listener?
    – Eido95
    Aug 20 '18 at 7:30
1

The listener (as you present it above) is simpler to use, and requires fewer resources (it doesn't require a thread). It's a very common pattern.

You can certainly use a Future, but I would perhaps suggest that in a lot of cases the simple synchronous callback is all that's required.

It does suffer from the issue that you'd normally expect the callback method to execute promptly (otherwise you're going to block execution on the main thread). If you're going to do a lot of work in a listener, I would perhaps then drop it into a new thread at that point.

1
  • Take a look at the OP's updated answer.
    – Spotted
    Sep 30 '16 at 12:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.