Say I have two heavyweight IO blocking operations, findMatchInSomeDB() and getDetailsFromOtherDB(String objectKey). Furthermore, I want them to run in the background, and use Guava Futures to chain them together because one depends on the result of the other (I know this could just be done sequentially in a simple Callable, but keeping it simple for illustration purposes):
Is there any practical or nuanced difference between the consumeChain and consumeChainAsync methods below?
import com.google.common.base.Function;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
public class Consumer
{
private final Retriever retriever;
private final ListeningExecutorService executorSvc;
public Consumer(Retriever retriever, ListeningExecutorService executorSvc)
{
this.retriever = retriever;
this.executorSvc = executorSvc;
}
private void consumeChain(String searchCriteria) throws Exception
{
ListenableFuture<String> futureMatch = executorSvc.submit(
() -> retriever.findMatchInSomeDB(searchCriteria));
Function<String, DataObject> keyToDataObj = objectKey ->
retriever.getDetailsFromOtherDB(objectKey);
// using "real" executor service so transform function runs
// in the background
ListenableFuture<DataObject> futureDataObj = Futures.transform(
futureMatch, keyToDataObj, executorSvc);
// ...do some other stuff in this thread...
// do something with futureDataObj result
futureDataObj.get();
}
private void consumeChainAsync(String searchCriteria) throws Exception
{
ListenableFuture<String> futureMatch = executorSvc.submit(
() -> retriever.findMatchInSomeDB(searchCriteria));
AsyncFunction<String, DataObject> keyToDataObj = objectKey ->
{
return executorSvc.submit(
() -> retriever.getDetailsFromOtherDB(objectKey));
};
// using DirectExecutor because the transform function
// only creates and submits a Callable
ListenableFuture<DataObject> futureDataObj = Futures.transformAsync(
futureMatch, keyToDataObj, MoreExecutors.directExecutor());
// ...do some other stuff in this thread...
// do something with futureDataObj
futureDataObj.get();
}
}
As far as I can tell, both will run each heavyweight operation via executorSvc, and both will propagate cancellation and/or execution failure.
It seems that the only point of transformAsync (instead of just using transform with an executor other than DirectExecutor) is for when you're working with an API that returns ListenableFuture instead of directly running the operation. Am I missing something?