I am trying to write a Single Thread Executor that returns a CompletableFuture
when a task gets scheduled and executes the task based on a PriorityBlockingQueue
.
My Tasks look like this:
public interface DriverTask<V> {
V call(WebDriver driver) throws Throwable;
default TaskPriority getPriority() {
return TaskPriority.LOW;
}
}
public enum TaskPriority {
HIGH,
MEDIUM,
LOW
}
Now my problem is when I use the CompletableFuture.supplyAsync
Method the Executor only gets a Runnable
and I dont know how to get my Executor to know the priority of the original Task.
Is there a different way to create a CompletableFuture
so that I get execute them based on priority?
The principle of methods like supplyAsync
is to create a new CompletableFuture
instance an setting up and asynchronous job which will eventually complete
the future.
You can do the same for your nontrivial setup:
private static WebDriver currentDriver() {
…
}
private static final ExecutorService BACKEND
= new ThreadPoolExecutor(1, 1, 1, TimeUnit.MINUTES, new PriorityBlockingQueue<>());
public static <V> CompletableFuture<V> runAsync(DriverTask<V> dt) {
CompletableFuture<V> result = new CompletableFuture<>();
class Job implements Runnable, Comparable<Job>,
CompletableFuture.AsynchronousCompletionTask {
public void run() {
try {
if(!result.isDone()) result.complete(dt.call(currentDriver()));
}
catch(Throwable t) { result.completeExceptionally(t); }
}
private TaskPriority priority() { return dt.getPriority(); }
public int compareTo(Job o) { return priority().compareTo(o.priority()); }
}
BACKEND.execute(new Job());
return result;
}
Note that implementing CompletableFuture.AsynchronousCompletionTask
is not necessary; it’s just a convention for marking those Runnable
implementations whose purpose is to complete a CompletableFuture
.
Another advantage of implementing the logic yourself is that this first stage does not need to wrap exceptions in a CompletionException
. So when the caller chains an exceptionally
, it will see the original, unwrapped exception. Also, a caller of join
would get a CompletionException
reflecting the code location of the join
call with the original exception as the cause, carrying much more useful information.
The purpose of if(!result.isDone())
before the actual completion attempt is to skip it if the CompletableFuture
has been cancelled (or otherwise completed) while waiting in the queue. Once the completion attempt has been started, canceling won’t interrupt it. This is the general behavior of CompletableFuture
.