avi
avi

Reputation: 195

main thread does not wait for CompletableFuture.runAsync() and returned the response

I have a function which has a invoke method , which internally calls an soap API , and it's taking like 22 seconds to execute , there are few other methods also in code , So totally deleteSoemthing()(code below) method takes like 24 seconds ,

now , i tried running the time taking method in a separate thread , So my assumption was even though its separate thread , it will just be optimize to 2 seconds because it was taking 22 seconds from total 24 seconds.

so instead of 24 seconds it might take 22 seconds because its running parallel.

but when i run this , through a postman , it takes only 2 seconds to execute , i mean the response is returned in 2 seconds. and the separate thread is keep running(when i check through debug).

So , my doubt is , does main thread does not wait for this task to complete and send the response back . or its just send the response and keep running the async task in background

void deleteSomething(){

CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
                try {
                    invoke("invoking a soap API"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });

//some other code

}

Upvotes: 1

Views: 5338

Answers (2)

Abhinaba Chakraborty
Abhinaba Chakraborty

Reputation: 3671

If you want the main thread (request) to process "some other code" and "invoke SOAP API" parallely and then combine and return the response to the end-user, then this wont work.

When we create a CompletableFuture instance, it spins off the computation in another thread and returns the Future immediately. If you need to block the result, then you need to call the get method on it. However this process will still take 22+2 = 24 secs to return the response.

To run the two tasks parallely, you should created two Callable(s) and submit them to ExecutorService

eg.

  public void deleteSomething(){
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    Collection<Callable<Void>> callables = new ArrayList<>();
    callables.add(() -> doSomeOtherTask());
    callables.add(() -> invokeSoapApi());
    try {
      List<Future<Void>> taskFutureList = executorService.invokeAll(callables);
      taskFutureList.get(0).get();
      taskFutureList.get(1).get();
    } catch (InterruptedException | ExecutionException e) {
      //error
    }
  }

  public Void doSomeOtherTask() {
    //some other code
    return null;
  }

  public Void invokeSoapApi() {
    //soap api call
    return null;
  }

Please note that thread pool should be created at the application startup. So if you actually wish to use it, then you should have "executorService" defined as instance variable. eg.

@Service
public class MyService {

  ...
  ...
  private ExecutorService executorService = Executors.newFixedThreadPool(2);
  ...
  ...
  //your methods
}

Upvotes: 1

Pankaj
Pankaj

Reputation: 2708

This is expected behaviour of CompletableFuture, if you checked the documentation it says -

/**
 * Returns a new CompletableFuture that is asynchronously completed
 * by a task running in the ForkJoinPool#commonPool() after
 * it runs the given action.
 *
 * @param runnable the action to run before completing the
 * returned CompletableFuture
 * @return the new CompletableFuture
 */

You can use blocking Future.get() to achieve what you want (as shown below)

void deleteSomething(){
    ExecutorService executorService = Executors.newCachedThreadPool();

    Future<Void> future = executorService.submit(() -> {
        invoke("Invoking soap API");
        return null;
    });
    
    //some other code

    future.get();
}

Creation of Thread pool in a method is not advisable as there is overhead associated with thread creation. Ideally, Thread pool should be created at the application startup.

Upvotes: 1

Related Questions