简体   繁体   English

在CompletableFuture中加入多个回调执行

[英]Join multiple callback executions in a CompletableFuture

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. 我想将此调用包装到CompletableFuture中,以具有Rx-ish API与其他异步操作组合。

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. Manager.discover()是第三方库的一种方法,实际上是本机函数的绑定,并且它在不同的线程中多次执行回调。

My DiscoveryCallback implements the following interface: My DiscoveryCallback实现以下接口:

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. 我试图将CompletableFuture<List<Result>>的实例注入DiscoveryCallback,然后调用complete方法。 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 ? 如何合并多个执行的结果并使包装返回单个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. 在Java 9中,可以对removeAsync返回的CompletableFuture使用.completeOnTimeout(null, timeout, unit)来具有超时机制。

Before Java 9, you need to schedule your own timeouts. 在Java 9之前,您需要安排自己的超时时间。 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. 您可以从此类中重构调度程序,以与其他类共享调度程序,也许将其分解为一个单例,该单例使用在.properties文件中设置的工厂,并且如果未配置,则使用示例中的默认值。

You can use a ReentrantLock instead of the synchronized statement to gain that little bit of performance. 您可以使用ReentrantLock而不是synchronized声明,以获得性能的那一点点。 It should only matter under heavy contention, but AsyncQueue<T> could be used for such purposes. 它仅在激烈争用下才有意义,但是AsyncQueue<T>可以用于此类目的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM