Method call to Future.get() blocks. Is that really desirable?
Asked Answered
T

5

94

Below is the snippet of the pseudo code. Does the below code not defeat the very notion of parallel asynchronous processing?

The reason I ask this is because in the below code the main thread would submit a task to be executed in a different thread. After submitting the task in the queue, it blocks on Future.get() method for the task to return the value. I would rather have the task executed in the main thread rather than submitting to a different thread and waiting for the results. What is that I gained by executing the task in a new thread?

I am aware that you could wait for a limited time etc, but then what if I really care about the result? The problem gets worse if there are multiple tasks to be executed. It seems to me that we are just doing the work synchronously. I am aware of the Guava library which provides a non blocking listener interface. But I am interested to know if my understanding is correct for the Future.get() API. If it is correct, why is the Future.get() designed to block thereby defeating the whole process of parallel processing?

I use Java 6.

public static void main(String[] args){

private ExectorService executorService = ...

Future future = executorService.submit(new Callable(){
    public Object call() throws Exception {
        System.out.println("Asynchronous Callable");
        return "Callable Result";
    }
});

System.out.println("future.get() = " + future.get());
}
Til answered 27/6, 2015 at 18:27 Comment(7)
The idea is that you would submit multiple tasks and then wait. You are right that if you wait for the result in between submitting each task, then they will be processed in serial and you won't gain anything.Inenarrable
@SkinnyJ If multiple tasks are submitted, how do you know the result returned is for which task? And how do i wait for multiple tasks?Til
@VishalP You will then have list of Futures which you could check with isDone() or fetch result with get()Since
The simplest is to use invokeAll. Otherwise, you can keep track of each future as you submit its Callable, and get the results one after the other. Either way, the processing is happening in parallel.Inenarrable
@TheMonk What made you you accept the answer? Is it because observer pattern is the solution to wait asynchronously?Zeeland
The important thing is that "get" is not starting the execution, "submit" is, get is only "waiting" the result. So we could start multiple tasks (multiple executors) and then use get on every one. In that way all the executors will run in parallel. If you can continue without the result instead of get you could use the Observer patternRisibility
I was wondering how get(..) would be working, but looking at the source code it just yields the processor for a preemption but literally doesn't release thread in the worst case. Anyone has worked with C++ (or other langs) which supports non-blocking wait with timeout featureBridwell
S
85

Future offers you method isDone() which is not blocking and returns true if computation has completed, false otherwise.

Future.get() is used to retrieve the result of computation.

You have a couple of options:

  • call isDone() and if the result is ready ask for it by invoking get(), notice how there is no blocking
  • block indefinitely with get()
  • block for specified timeout with get(long timeout, TimeUnit unit)

The whole Future API thing is there to have easy way obtaining values from threads executing parallel tasks. This can be done synchronously or asynchronously if you prefer, as described in bullets above.

UPDATE WITH CACHE EXAMPLE

Here is a cache implementation from Java Concurrency In Practice, an excellent use case for Future.

  • If the computation is already running, caller interested in result of computation will wait for computation to finish
  • If the result is ready in the cache, caller will collect it
  • if the result is not ready and computation has not started yet, caller will start computation and wrap result in Future for other callers.

This is all easily achieved with Future API.

package net.jcip.examples;

import java.util.concurrent.*;
/**
 * Memoizer
 * <p/>
 * Final implementation of Memoizer
 *
 * @author Brian Goetz and Tim Peierls
 */
public class Memoizer <A, V> implements Computable<A, V> {
    private final ConcurrentMap<A, Future<V>> cache
            = new ConcurrentHashMap<A, Future<V>>();
    private final Computable<A, V> c;

public Memoizer(Computable<A, V> c) {
    this.c = c;
}

public V compute(final A arg) throws InterruptedException {
    while (true) {

        Future<V> f = cache.get(arg);
        // computation not started
        if (f == null) {
            Callable<V> eval = new Callable<V>() {
                public V call() throws InterruptedException {
                    return c.compute(arg);
                }
            };

            FutureTask<V> ft = new FutureTask<V>(eval);
            f = cache.putIfAbsent(arg, ft);
            // start computation if it's not started in the meantime
            if (f == null) {
                f = ft;
                ft.run();
            }
        }

        // get result if ready, otherwise block and wait
        try {
            return f.get();
        } catch (CancellationException e) {
            cache.remove(arg, f);
        } catch (ExecutionException e) {
            throw LaunderThrowable.launderThrowable(e.getCause());
        }
    }
  }
}
Since answered 27/6, 2015 at 18:39 Comment(7)
But then you still wait in a loop checking isDone(), isn't this blocking as well?Til
This entirely depends on your design. You don't have to. For example you could have one thread dispatching events as they come into system, and also periodically check for result. In this scenario checking for results if they are not ready would not block and thread could continue with dispatching events. There are many patterns which could be used here to avoid blocking. Check Observer Pattern which could be used to notify of result, also Active Object Pattern and Half Sync - Half Async, Publish Subscribe. Try to read up a bit on these patterns to get idea how to use asynchronous programming.Since
You are right. But I still wonder if this is made unnecessarily complex. An out of the box listener solution would have been very handy.Til
This Listener would most likely be Observer in a simple Observer Pattern. You can probably figure that one in 20 minutes on your own:) This might be helpful : tutorialspoint.com/design_pattern/observer_pattern.htmSince
sure thnx a ton for the valuable info. Very much appreciated :)Til
Assuming, you want timeout involved, then I think as @SkinnyJ pointed out you can simply use invokeAll as it does the same thing as isDone & get combo. You can then use Future.isCancelled before using get.Munt
baeldung.com/java-future - this is simple and yet great example.Noncontributory
T
13

Below is the snippet of the pseudo code. My question is- Does the below code not defeat the very notion of parallel asynchronous processing?

It all depends on your use case:

  1. If you really want to block till you get the result, use blocking get()

  2. If you can wait for a specific period to know the status instead of infinite blocking duration, use get() with time-out

  3. If you can continue without analysing the result immediately and inspect the result at future time, use CompletableFuture (java 8)

    A Future that may be explicitly completed (setting its value and status), and may be used as a CompletionStage, supporting dependent functions and actions that trigger upon its completion.

  4. You can implement callback mechanism from your Runnable/Callable. Have a look at below SE question:

    Java executors: how to be notified, without blocking, when a task completes?

Tee answered 26/12, 2016 at 10:51 Comment(2)
thnx @ravnidra. I wasn't using 1.8 then, so CompletableFuture was out of question. But the other inputs are pretty relevant.Til
Hi @Ravindra, is there a way to keep waiting using get() method but still keep the UI interacting. In my case, if get() method is called, my app dosent give callback of onBackPressed on Dialog.Garboard
E
8

I would like to give my share on this one, more on theoretical point of view as there are some technical answers already. I would like to base my answer on the comment:

Let me give you my example. The tasks I submit to the service end up raising HTTP requests, The result of the HTTP request can take a lot of time. But I do need the result of each HTTP request. The tasks are submitted in a loop. If I wait for each task to return (get), then I am loosing parallelism here, ain't I?

which agrees with what is said in the question.

Say you have three kids, and you want to make a cake, for your birthday. Since you want to make the greatest of cakes you need a lot of different stuff to prepare it. So what you do is split the ingredients on three different lists, because where you live there exist just 3 supermarkets that sell different products, and assign each of your kids a single task, simultaneously.

Now, before you can start preparing the cake (let's assume again, that you need all the ingredients beforehand) you will have to wait for the kid that have to do the longest route. Now, the fact that you need to wait for all the ingredients before starting to make the cake is your necessity, not a dependency among tasks. Your kids have been working on the tasks simoultaneously as long as they could (e.g: until the first kid completed the task). So, to conclude, here you have the paralelilsm.

The sequential example is described when you have 1 kid and you assign all three tasks to him/her.

Earle answered 11/5, 2018 at 16:29 Comment(0)
D
3

In the example you have given you might as well run everything in your main() method and go your merry way.

But let us assume you have three steps of computation that you are currently running sequentially. Just for understanding let us assume that step1 takes t1 seconds, step2 takes t2 seconds, and step3 takes t3 seconds to complete. So total computation time is t1+t2+t3. Also, let us assume that t2>t1>=t3.

Now let us consider a scenario when we executed these three steps in parallel using Future to hold each computational results. You can check if each task is done using non-blocking isDone() call on corresponding futures. Now what happens? theoretically your execution is as fast as how t2 completes right? So we did gain some benefits from parallelism.

Also, in Java8 , there is CompletableFuture that supports functional style call backs.

Dichroite answered 27/6, 2015 at 18:39 Comment(2)
Let me give you my example. The tasks I submit to the service end up raising HTTP requests, The result of the HTTP request can take a lot of time. But I do need the result of each HTTP request. The tasks are submitted in a loop. If I wait for each task to return (get), then I am loosing parallelism here, ain't I ?Til
No, invoke get only when your isDone() returns true. That is better than executing all the calls sequentially. So essentially, your execution will be only as fast as the slowest performing web service (including network etc)Dichroite
R
1

If you don't care about the results, then spawn a new thread and from that thread use ExectorService API for task submission. In that way, your parent thread i.e main thread will not be blocking in any way, it would simply spawn a new thread and then will start further execution, while the new thread will submit your tasks.

For creating new thread - either do it yourself by having a ThreadFactory for your async thread creation or use some implementation of java.util.concurrent.Executor.

If this is in a JEE application and you are using Spring framework then you can easily create a new asynchronous thread using @async annotation.

Hope this helps!

Roughrider answered 27/6, 2015 at 19:0 Comment(1)
@VishalP Have got some answer?Roughrider

© 2022 - 2024 — McMap. All rights reserved.