nus-cs2030 / 2223-s1

MIT License
1 stars 0 forks source link

thenApplyAsync #336

Open wjiayis opened 1 year ago

wjiayis commented 1 year ago

Hi there,

I modified a question from a past year paper to understand how thenApplyAsync works.

class Code {
    public static void main(String[] args) {
        CompletableFuture<Integer> ten = CompletableFuture.supplyAsync(() -> 10);
        ten.thenApplyAsync(plus(1))
            .thenApplyAsync(plus(50))
            .thenApplyAsync(plus(20)).join();
    }

    private static Function<Integer,Integer> plus(int i) {
        return x -> {
            doSomething(i);
            System.out.println(x + i);
            return x + i;
        };
    }

    private static void doSomething(int i) {
        try {
            Thread.sleep(i * 100);
        } catch (InterruptedException e) {}
    }
}

Output is

11
61
81

with some latency before the printing of both 61 and 81.

I see why there is a latency before the printing of 61, I don't really understand why there is latency too before the printing of 81.

Would appreciate some help! Thanks in advance:)

JuliaPoo commented 1 year ago

When chaining thenApply or thenApplyAsync, the result has to arrive before the callback can be executed. In your case

ten.thenApplyAsync(plus(1)) // `plus(1)` executes only after `ten` has arrived (immediate because it's a completed future)
    .thenApplyAsync(plus(50)) // `plus(50)` executes only after the previous `plus(1)` has been executed
    .thenApplyAsync(plus(20)).join(); // `plus(20)` executes only after the previous `plus(20)` has been executed

Then is because thenApply and thenApplyAsync is used to chain operations. E.g., if you wanna compute f(g(x)), you need to compute g(x) before u can compute f(g(x)). The same thing is happening here.

By the way, a little misnomer is that thenApply and thenApplyAsync are actually equally 'async'. The difference between the two is how java handles the scheduling. E.g, in thenApplyAsync, you're able to specify which thread pool to execute the function.

For our module, it's not really useful? An example use case for the ___Async variants is if you have a server that has two main components, one of which is say a "network component" that handles incoming requests and responds to them. If you want the server to be responsive, you'd never schedule CPU-intensive operations on the thread pool that handles the network component, because then you'll be blocking operations that should be handled first. In this case you don't want to use java's default scheduler, and would use the ___Async variants for that additional control.

yewey2 commented 1 year ago

Interesting note about thenApplyAsync vs thenApply!

So to have lesser latency between 61 and 81, should the code be modified as below, keeping the spirit that the final sum is 81?

    public static void main(String[] args) {
        CompletableFuture<Integer> zero = CompletableFuture.supplyAsync(() -> 0);

        CompletableFuture<Integer> ten = CompletableFuture.supplyAsync(() -> 10);
        CompletableFuture<Integer> eleven = ten.thenApplyAsync(plus(1));

        CompletableFuture<Integer> sixtyone = eleven.thenApplyAsync(plus(50));
        CompletableFuture<Integer> twenty=zero.thenApplyAsync(plus(20)); // this prints 20 
        sixtyone.thenCombine(twenty, (a, b) -> plus(b).apply(a)).join();
    }

Edit: forgot semicolons Edit2: realised the code has a side effect of printing 20... any way to circumvent this?