如何在CompletionStage.exceptionally中链接非阻塞操作

时间:2017-06-14 02:24:18

标签: java asynchronous exception-handling java-8 completable-future

我正在用Java编写一个Play2应用程序服务方法,该方法应该执行以下操作。异步调用方法A,如果失败,则异步调用方法B.

为了说明假设服务调用的后端的这个接口:

public interface MyBackend {
    CompletionStage<Object> tryWrite(Object foo);
    CompletionStage<Object> tryCleanup(Object foo);
}

所以在我的服务方法中,我想返回一个可以完成这些的Future:

  • tryWrite成功完成
  • tryWrite失败并且tryCleanup成功完成并失败,但tryWrite()除外

(注意:当然tryWrite()可以自行进行任何清理,这是一个用来说明问题的简化示例)

像这样调用后端的服务的实现对我来说似乎很难,因为CompletionStage.exceptionally()方法不允许Composing。

版本1:

public class MyServiceImpl {
    public CompletionStage<Object> tryWriteWithCleanup(Object foo) {

        CompletionStage<Object> writeFuture = myBackend.tryWrite(foo)
            .exceptionally((throwable) -> {
                CompletionStage<Object> cleanupFuture = myBackend.tryCleanup(foo);
                throw new RuntimeException(throwable);
        });
        return writeFuture;
    }
}

因此版本1以非阻塞方式调用tryCleanup(foo),但tryWriteWithCleanup()返回的CompletionStage不会等待cleanupFuture完成。如何更改此代码以从等待cleanupFuture完成的服务返回未来?

第2版:

public class MyServiceImpl {
    public CompletionStage<Object> tryWriteWithCleanup(Object foo) {

        final AtomicReference<Throwable> saveException = new AtomicReference<>();
        CompletionStage<Object> writeFuture = myBackend
            .tryWrite(foo)
            .exceptionally(t -> {
                saveException.set(t);
                // continue with cleanup
                return null;
            })
            .thenCompose((nil) -> {
                // if no cleanup necessary, return
                if (saveException.get() == null) {
                    return CompletableFuture.completedFuture(null);
                }
                return CompletionStage<Object> cleanupFuture = myBackend.tryCleanup(foo)
                    .exceptionally(cleanupError -> {
                        // log error
                        return null;
                    })
                    .thenRun(() -> {
                        throw saveException.get();
                    });
        });
        return writeFuture;
    }
}

Version2使用外部AtomicReference来存储失败,并在另一个thenCompose()块中进行异步第二次调用(如果发生故障)。

我所有其他尝试这样做的结果都非常笨拙,以至于我不想在这里粘贴它们。

2 个答案:

答案 0 :(得分:7)

不幸的是,CompletionStage / CompletableFuture不提供具有组合的异常处理API。

您可以通过依靠handle()BiFunction返回CompletionStage来解决此问题。这将为您提供嵌套阶段(CompletionStage<CompletionStage<Object>>),您可以使用&#34; 不需要&#34;使用compose(identity())

public CompletionStage<Object> tryWriteWithCleanup(Object foo) {
    return myBackend.tryWrite(foo)
            .handle((r, e) -> {
                if (e != null) {
                    return myBackend.tryCleanup(foo)
                            .handle((r2, e2) -> {
                                // Make sure we always return the original exception
                                // but keep track of new exception if any,
                                // as if run in a finally block
                                if (e2 != null) {
                                    e.addSuppressed(e2);
                                }
                                // wrapping in CompletionException  behaves as if
                                // we threw the original exception
                                throw new CompletionException(e);
                            });
                }
                return CompletableFuture.completedFuture(r);
            })
            .thenCompose(Function.identity());
}

答案 1 :(得分:1)

您可能只是等待处理程序内的完成:

public CompletionStage<Object> tryWriteWithCleanup(Object foo) {
    return myBackend.tryWrite(foo).exceptionally(throwable -> {
        myBackend.tryCleanup(foo).toCompletableFuture().join();
        throw new CompletionException(throwable);
    });
}

这会将结果CompletionStage的完成推迟到清理阶段的完成。使用CompletionException作为包装将使包装对调用者透明。

然而,它有一些缺点。虽然框架可能在等待或产生补偿线程时利用线程,但如果它是工作线程,如果tryWrite返回的阶段恰好在输入时exceptionally已经完成,则被阻塞的线程可能是调用者线程}}。不幸的是,没有exceptionallyAsync方法。您可以使用handleAsync代替,但它会使代码复杂化,同时仍然感觉像是一个kludge。

此外,清理引发的异常可能会影响原始故障。

更清洁的解决方案可能会涉及更多:

public CompletionStage<Object> tryWriteWithCleanup(Object foo) {

    CompletableFuture<Object> writeFuture = new CompletableFuture<>();

    myBackend.tryWrite(foo).whenComplete((obj,throwable) -> {
        if(throwable==null)
            writeFuture.complete(obj);
        else
            myBackend.tryCleanup(foo).whenComplete((x,next) -> {
                try {
                    if(next!=null) throwable.addSuppressed(next);
                }
                finally {
                    writeFuture.completeExceptionally(throwable);
                }
        });
    });
    return writeFuture;
}

这只是手动创建一个CompletableFuture,允许控制它的完成,这可以直接由成功案例中链接到tryWrite阶段的动作发生,也可以通过链接到特殊情况下的清理阶段。请注意,后者负责通过addSuppressed链接可能的后续清理异常。