关闭CompletableFuture链

时间:2016-11-16 09:26:52

标签: java java-8 completable-future

我正在寻找更好的方法来“关闭”某些资源,这里摧毁Process链中的外部CompletableFuture。现在我的代码大致如下:

public CompletableFuture<ExecutionContext> createFuture()
{
    final Process[] processHolder = new Process[1];
    return CompletableFuture.supplyAsync(
            () -> {
                try {
                    processHolder[0] = new ProcessBuilder(COMMAND)
                            .redirectErrorStream(true)
                            .start();
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
                return PARSER.parse(processHolder[0].getInputStream());
            }, SCHEDULER)
            .applyToEither(createTimeoutFuture(DURATION), Function.identity())
            .exceptionally(throwable -> {
                processHolder[0].destroyForcibly();
                if (throwable instanceof TimeoutException) {
                    throw new DatasourceTimeoutException(throwable);
                }
                Throwables.propagateIfInstanceOf(throwable, DatasourceException.class);
                throw new DatasourceException(throwable);
            });
}

我看到的问题是一个“hacky”单元素数组,它保存对进程的引用,以便在出错时可以关闭它。是否有一些CompletableFuture API允许将一些“上下文”传递给exceptionally(或其他一些方法来实现)?

我正在考虑自定义CompletionStage实现,但删除“holder”变量似乎是一项重大任务。

2 个答案:

答案 0 :(得分:2)

不需要CompletableFuture s的线性链。实际上,你已经没有因为createTimeoutFuture(DURATION)因为实现超时而非常复杂。你可以这样说:

public CompletableFuture<ExecutionContext> createFuture() {
    CompletableFuture<Process> proc=CompletableFuture.supplyAsync(
        () -> {
            try {
                return new ProcessBuilder(COMMAND).redirectErrorStream(true).start();
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }, SCHEDULER);
    CompletableFuture<ExecutionContext> result
        =proc.thenApplyAsync(process -> PARSER.parse(process.getInputStream()), SCHEDULER);
    proc.thenAcceptAsync(process -> {
        if(!process.waitFor(DURATION, TimeUnit.WHATEVER_DURATION_REFERS_TO)) {
            process.destroyForcibly();
            result.completeExceptionally(
                new DatasourceTimeoutException(new TimeoutException()));
        }
    });
    return result;
}

如果你想保持未来的未来,也许你认为流程启动时间很重要,你可以使用

public CompletableFuture<ExecutionContext> createFuture() {
    CompletableFuture<Throwable> timeout=createTimeoutFuture(DURATION);
    CompletableFuture<Process> proc=CompletableFuture.supplyAsync(
        () -> {
            try {
                return new ProcessBuilder(COMMAND).redirectErrorStream(true).start();
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }, SCHEDULER);
    CompletableFuture<ExecutionContext> result
        =proc.thenApplyAsync(process -> PARSER.parse(process.getInputStream()), SCHEDULER);
    timeout.exceptionally(t -> new DatasourceTimeoutException(t))
           .thenAcceptBoth(proc, (x, process) -> {
                if(process.isAlive()) {
                    process.destroyForcibly();
                    result.completeExceptionally(x);
                }
            });
    return result;
}

答案 1 :(得分:1)

我自己使用了一个项目数组来模拟Java中适当的闭包。

另一个选择是使用带字段的私有静态类。优点是它使目的更清晰,对具有大封闭的垃圾收集器的影响稍小,即具有N个字段的对象与长度为1的N个数组。如果您需要关闭相同的字段,它也会变得有用在其他方法。

这是事实上的模式,甚至超出CompletableFuture的范围,并且在lambdas是Java中的东西之前很久就已经(ab)使用了它,例如匿名课程。所以,不要感到如此糟糕,只是Java的进化并没有为我们提供适当的封闭(但是?永远?)。

如果需要,可以从CompletableFuture内的.handle()返回值,这样就可以完整地包装完成结果并返回一个包装器。在我看来,这并不比手动闭包更好,并补充说你将来会创造这样的包装。

不需要对CompletableFuture进行子类化。你对改变它的行为不感兴趣,只是在附加数据时,你可以用当前Java的最终变量捕获来做。也就是说,除非你剖析并看到创建这些闭包实际上在某种程度上影响了性能,我非常怀疑。