Reputation: 86
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?
Upvotes: 5
Views: 2871
Reputation: 3962
It seems that the only point of
transformAsync
(instead of just usingtransform
with an executor other than DirectExecutor) is for when you're working with an API that returns ListenableFuture instead of directly running the operation.
I think that's the idea.
However, I can think of a small difference that makes transformAsync
slightly better: If you call cancel(true)
on the output Future
, transform
currently won't interrupt the thread that runs getDetailsFromOtherDB
. In contrast, transformAsync
will (by calling cancel(true)
on the ListenableFuture
returned from the ListeningExecutorService
). transform
should propagate interruption, but there are some subtleties to getting this right, covered in the link above.
Upvotes: 1