I expose a method in a library, which returns a CompletableFuture. That method's computation happens on a single-threaded Executor which is my bottleneck, hence I do not want any subsequent work to happen on the same thread.
If I use the simple approach of returning the result of "supplyAsync", I'll be exposing my precious thread to the callers, who may be adding synchronous operations (e.g. via thenAccept) which could take some CPU time on that thread.
Repro below:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CfPlayground {
private ExecutorService preciousExecService = Executors.newFixedThreadPool(1);
CfPlayground() {}
private static void log(String msg) {
System.out.println("[" + Thread.currentThread().getName() + "] " + msg);
}
CompletableFuture<String> asyncOp(String param) {
return CompletableFuture.supplyAsync(() -> {
log("In asyncOp");
return "Hello " + param;
}, preciousExecService);
}
void syncOp(String salutation) {
log("In syncOp: " + salutation);
}
void run() {
log("run");
asyncOp("world").thenAccept(this::syncOp);
}
public static void main(String[] args) throws InterruptedException {
CfPlayground compFuture = new CfPlayground();
compFuture.run();
Thread.sleep(500);
compFuture.preciousExecService.shutdown();
}
}
This indeed prints:
[main] run
[pool-1-thread-1] In asyncOp
[pool-1-thread-1] In syncOp: Hello world
One solution I found was to introduce another Executor, and add a no-op thenApplyAsync with that executor before returning the CompletableFuture
CompletableFuture<String> asyncOp(String param) {
return CompletableFuture.supplyAsync(() -> {
log("In asyncOp");
return "Hello " + param;
}, preciousExecService).thenApplyAsync(s -> s, secondExecService);
}
This works, but doesn't feel super elegant - is there a better way to do this?