Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chain a series of asynchronous calls

I have a series of asynchronous operations

private void doSomething(){
  get("/something", new Callback(){
    void onComplete(String data){
      updateUi(something, data);           
      doSomethingElse();
    }
  });
}

private void doSomethingElse(){
  get("/something/else", new Callback(){
    void onComplete(String data){
      updateUi(somethingElse, data);
      doYetAnotherThing();
    }
  });
}

private void doYetAnotherThing(){
  get("/yet/another/thing", new Callback(){
    void onComplete(String data){
      updateUi(yetAnotherThing, data);
      allDone();
    }
  });
}

This suffers from few problems:

  1. Cannot reuse any of the callbacks elsewhere since each is intrinsically tied to the "next step"
  2. Re-ordering operations or inserting another operation is non-intuitive and involves jumping all over the place.

I have looked at the following options to mitigate this:

  1. ExecuterService#invokeAll - I don't see how this solution can be used without blocking.
  2. RxJava - I would prefer to avoid such a paradigm shift in my application if I can!
  3. Guava's ListenableFutures and its transform method. I saw this referred to in few places around the interwebs nut I honestly don't see how this would solve my problem.

So, the question is: What would be a good pattern to chain a series of asynchronous calls in Java? Looking for a solution that works with Java 7 since I need this for an Android app.

like image 239
curioustechizen Avatar asked Sep 26 '14 12:09

curioustechizen


3 Answers

There certainly is some guessing involved, regarding the actual intention and use-case where you encountered this problem. Additionally, it is not entirely clear what something, somethingElse and yetAnotherThing are (where they come from and where they should go).

However, based on the information that you provided, and as an addition to (or rather extension or generalization of) the answer by slartidan: The difference between these dummy calls that you sketched there seem to be

  • The String argument that is passed to the get method
  • The Callback that is called
  • Which method is executed next

You could factor out these parts: The String argument and the Callback could be passed as parameters to a general method that creates a Callable. The sequence of the calls could simply be defined by placing these Callable objects into a list, in the appropriate order, and execute them all with a single threaded executor service.

As you can see in the main method of this example, the sequence of calls can then be configured rather easily:

import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class ChainedAsyncTest {

    public static void main(String[] args) throws InterruptedException {
        ChainedAsyncTest t = new ChainedAsyncTest();
        ExecutorService e = Executors.newFixedThreadPool(1);
        e.invokeAll(Arrays.asList(
            t.call("/something", t.somethingCallback),
            t.call("/something/else", t.somethingElseCallback),
            t.call("/yet/another/thing", t.yetAnotherThingCallback),
            t.allDone()));
    }

    private Callback somethingCallback = new Callback() {
        @Override
        public void onComplete(String data) {
            updateUi("something", data);
        }
    };

    private Callback somethingElseCallback = new Callback() {
        @Override
        public void onComplete(String data) {
            updateUi("somethingElse", data);
        }
    };

    private Callback yetAnotherThingCallback = new Callback() {
        @Override
        public void onComplete(String data) {
            updateUi("yetAnotherThing", data);
        }
    };

    private Callable<Void> call(
        final String key, final Callback callback) {
        return new Callable<Void>() {
            @Override
            public Void call() {
                get(key, callback);
                return null;
            }
        };
    }

    private Callable<Void> allDone() {
        return new Callable<Void>() {
            @Override
            public Void call() {
                System.out.println("allDone");
                return null;
            }
        };
    }



    interface Callback
    {
        void onComplete(String data);
    }
    private void get(String string, Callback callback) {
        System.out.println("Get "+string);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        callback.onComplete("result of "+string);
    }
    private void updateUi(String string, String data) {
        System.out.println("UpdateUI of "+string+" with "+data);
    }

}

(The example uses invokeAll, which blocks until all tasks have been executed. This could be solved differently to be really non-blocking at the call site. The main idea is to create a list of the tasks, which are all created by the same method call)

like image 88
Marco13 Avatar answered Oct 24 '22 03:10

Marco13


Spontainious thought: You could define the chained calls as a method parameter to make your methods reusable. Here is my example code:

public class Scribble {

    final Callback step1 = new Callback() {
        void onComplete(String string) {
            doSomethingElse(step2);
        };
    };

    final Callback step2 = new Callback() {
        void onComplete(String string) {
            doYetAnotherThing(step3);
        };
    };

    final Callback step3 = new Callback() {
        void onComplete(String string) {
            allDone();
        }
    };

    private void start() {
        doSomething(step1);
    }

    private void doSomething(final Callback externalCallback) {
        get("/something", new Callback() {
            void onComplete(String data) {
                updateUi(something, data);
                externalCallback.onComplete(data);
            }
        });
    }

    private void doSomethingElse(final Callback externalCallback) {
        get("/something/else", new Callback() {
            void onComplete(String data) {
                updateUi(somethingElse, data);
                externalCallback.onComplete(data);
            }
        });
    }

    private void doYetAnotherThing(final Callback externalCallback) {
        get("/yet/another/thing", new Callback() {
            void onComplete(String data) {
                updateUi(yetAnotherThing, data);
                externalCallback.onComplete(data);
            }
        });
    }

    // - the code below is only to make everything compilable -

    public class Callback {

        void onComplete(String string) {
        }

    }

    private Object something;
    protected Object somethingElse;
    protected Object yetAnotherThing;

    protected void allDone() {
        System.out.println("Scribble.allDone()");
    }

    protected void updateUi(Object yetAnotherThing2, String data) {
        System.out.println("Scribble.updateUi()"+data);
    }

    private void get(String string, Callback callback) {
        System.out.println("get "+string);
        callback.onComplete(string);
    }

    public static void main(String[] args) {
        new Scribble().start();
    }

}
like image 33
slartidan Avatar answered Oct 24 '22 02:10

slartidan


I totally support the approved answer, but I'm also tossing in something I created for these types of problems that comes in handy when you start adding conditional logic within your chain of asynchronous actions. I recently fermented this into a simple library (jasync-driver).

Here is how you'd wire up your example. As you can see, each task has no knowledge of the task that follows. In contrast to the approved answer, the chaining of the tasks is done through a simple synchronous (...looking) method body instead of a list.

public void doChainedLogic() {

    final AsyncTask<Void, Void> doSomething = new AsyncTask<Void, Void>() {
        @Override
        public void run(Void arg, final ResultHandler<Void> resultHandler) {
            get("/something", new Callback() {
                public void onComplete(String data) {
                    updateUi(something, data);
                    resultHandler.reportComplete();
                }
            });
        }
    };

    final AsyncTask<Void, Void> doSomethingElse = new AsyncTask<Void, Void>() {
        @Override
        public void run(Void arg, final ResultHandler<Void> resultHandler) {
            get("/something/else", new Callback() {
                public void onComplete(String data) {
                    updateUi(somethingElse, data);
                    resultHandler.reportComplete();
                }
            });
        }
    };

    final AsyncTask<Void, Void> doYetAnotherThing = new AsyncTask<Void, Void>() {
        @Override
        public void run(Void arg, final ResultHandler<Void> resultHandler) {
            get("/yet/another/thing", new Callback() {
                public void onComplete(String data) {
                    updateUi(yetAnotherThing, data);
                    resultHandler.reportComplete();
                }
            });
        }
    };

    // This looks synchronous, but behind the scenes JasyncDriver is
    // re-executing the body and skipping items already executed.
    final JasyncDriver driver = new JasyncDriver();
    driver.execute(new DriverBody() {
        public void run() {
            driver.execute(doSomething);
            driver.execute(doSomethingElse);
            driver.execute(doYetAnotherThing);
        }
    });
}

Now here's a tweak to the example that includes some conditional logic that depends upon an asynchronous result:

final AsyncTask<Void, String> checkSomething = new AsyncTask<Void, String>() {
    @Override
    public void run(Void arg, final ResultHandler<String> resultHandler) {
        get("/check/something", new Callback() {
            public void onComplete(String data) {
                resultHandler.reportComplete(data);
            }
        });
    }
};

final JasyncDriver driver = new JasyncDriver();
driver.execute(new DriverBody() {
    public void run() {
        driver.execute(doSomething);
        if ("foobar".equals(driver.execute(checkSomething))) {
            driver.execute(doSomethingElse);
        }
        driver.execute(doYetAnotherThing);
    }
});

As you can see, asynchronous conditional logic is as simple as writing a standard if statement.

like image 1
Ryan Avatar answered Oct 24 '22 02:10

Ryan