I have the following working code:
DiscoveryCallback callback = new DiscoveryCallback();
Manager.discover(someparam, callback);
I want to wrap this call into a CompletableFuture to have a Rx-ish API to compose with other async operations.
Manager.discover() is a method of a third-party library that is actually a binding for native functions and it executes the callback multiple times, in different threads.
My DiscoveryCallback implements the following interface:
interface onFoundListerner {
onFound(List<Result> results)
onError(Throwable error)
}
I tried to inject an instance of CompletableFuture<List<Result>>
into DiscoveryCallback and then call the complete method. It works fine for one callback execution, the others are ignored.
How can I join the results of this multiple executions and make my wrapper return a single CompletableFuture ?
What about an asynchronous queue?
public class AsyncQueue<T> {
private final Object lock = new Object();
private final Queue<T> queue = new ArrayDeque<T>();
private CompletableFuture<Void> removeCf = new CompletableFuture<>();
public void add(T item) {
synchronized (lock) {
queue.add(item);
removeCf.complete(null);
}
}
public CompletableFuture<T> removeAsync() {
CompletableFuture<Void> currentCf = null;
synchronized (lock) {
T item = queue.poll();
if (item != null) {
return CompletableFuture.completedFuture(item);
}
else {
if (removeCf.isDone()) {
removeCf = new CompletableFuture<>();
}
currentCf = removeCf;
}
}
return currentCf
.thenCompose(v -> removeAsync());
}
}
In Java 9, you can use .completeOnTimeout(null, timeout, unit)
on the CompletableFuture
returned by removeAsync
to have a timeout mechanism.
Before Java 9, you need to schedule your own timeouts. Here's a version with an embedded timeout scheduler:
public class AsyncQueue<T> {
static final ScheduledExecutorService scheduledExecutorService;
static {
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1, new ScheduledThreadFactory());
scheduledThreadPoolExecutor.setRemoveOnCancelPolicy(true);
scheduledExecutorService = Executors.unconfigurableScheduledExecutorService(scheduledThreadPoolExecutor);
}
static final class ScheduledThreadFactory implements ThreadFactory {
static AtomicInteger scheduledExecutorThreadId = new AtomicInteger(0);
static final synchronized int nextScheduledExecutorThreadId() {
return scheduledExecutorThreadId.incrementAndGet();
}
@Override
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable, "AsynchronousSemaphoreScheduler-" + nextScheduledExecutorThreadId());
thread.setDaemon(true);
return thread;
}
}
private final Object lock = new Object();
private final Queue<T> queue = new ArrayDeque<T>();
private CompletableFuture<Long> removeCf = new CompletableFuture<>();
public void add(T item) {
synchronized (lock) {
queue.add(item);
removeCf.complete(System.nanoTime());
}
}
public CompletableFuture<T> removeAsync(long timeout, TimeUnit unit) {
if (unit == null) throw new NullPointerException("unit");
CompletableFuture<Long> currentCf = null;
synchronized (lock) {
T item = queue.poll();
if (item != null) {
return CompletableFuture.completedFuture(item);
}
else if (timeout <= 0L) {
return CompletableFuture.completedFuture(null);
}
else {
if (removeCf.isDone()) {
removeCf = new CompletableFuture<>();
}
currentCf = removeCf;
}
}
long startTime = System.nanoTime();
long nanosTimeout = unit.toNanos(timeout);
CompletableFuture<T> itemCf = currentCf
.thenCompose(endTime -> {
long leftNanosTimeout = nanosTimeout - (endTime - startTime);
return removeAsync(leftNanosTimeout, TimeUnit.NANOSECONDS);
});
ScheduledFuture<?> scheduledFuture = scheduledExecutorService
.schedule(() -> itemCf.complete(null), timeout, unit);
itemCf
.thenRun(() -> scheduledFuture.cancel(true));
return itemCf;
}
public CompletableFuture<T> removeAsync() {
CompletableFuture<Long> currentCf = null;
synchronized (lock) {
T item = queue.poll();
if (item != null) {
return CompletableFuture.completedFuture(item);
}
else {
if (removeCf.isDone()) {
removeCf = new CompletableFuture<>();
}
currentCf = removeCf;
}
}
return currentCf
.thenCompose(endTime -> removeAsync());
}
}
You can refactor the scheduler out of this class to share it with other classes, perhaps into a singleton which uses a factory set up in a .properties
file and which resorts to the default in the example if not configured.
You can use a ReentrantLock
instead of the synchronized
statement to gain that little bit of performance. It should only matter under heavy contention, but AsyncQueue<T>
could be used for such purposes.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.