如何处理Java 8 CompletableFuture中的错误?

如何处理Java 8 CompletableFuture中的错误?,java,multithreading,concurrency,java-8,completable-future,Java,Multithreading,Concurrency,Java 8,Completable Future,我正试图在Java中的WebSocket上实现一个简单的类似RPC(或请求-响应)的系统(前端将有JS,但现在我正在后端工作) 我正在尝试应用JavaCompletableFuture模式来处理异步发送消息。但我现在被错误处理所困扰 我有一个类(我们称之为rpc类),负责通过WebSocket会话发送消息(这里使用SpringWebSocket支持类),然后等待“回复”类型的消息,并将它们与挂起的请求相匹配,并将内容返回给调用者 流程是: 客户端代码调用rpc类上的方法,指定要在远程进程上调用

我正试图在Java中的WebSocket上实现一个简单的类似RPC(或请求-响应)的系统(前端将有JS,但现在我正在后端工作)

我正在尝试应用Java
CompletableFuture
模式来处理异步发送消息。但我现在被错误处理所困扰

我有一个类(我们称之为rpc类),负责通过WebSocket会话发送消息(这里使用SpringWebSocket支持类),然后等待“回复”类型的消息,并将它们与挂起的请求相匹配,并将内容返回给调用者

流程是:

  • 客户端代码调用rpc类上的方法,指定要在远程进程上调用的过程的名称、要将消息发送到的会话以及要发送的参数映射
  • rpc类使用另一个较低级别的类使用
    执行器(线程池)异步发送消息,并接收“发送消息”操作的
    CompletableFuture
  • 它将挂起的请求存储在映射中,构建一个
    CompletableFuture
    ,并将其与挂起的请求关联,然后将它们存储在映射中。它返回可完成的未来
  • 当收到“reply”类型的消息时,在同一类上调用一个方法,该方法尝试将响应与一个挂起的请求(它们对此有一个ID)匹配,然后使用响应中接收的内容完成
    CompletableFuture
因此涉及到3个线程:调用线程、发送消息的线程和接收消息并完成未来的线程

现在,我应该如何处理消息发送中的错误(例如IO错误),以使返回的
completableFuture
也失败(或者可能实施重试策略和超时

以下是发送消息的rpc类方法的代码:

/**
 * Invoke a remote procedure over WS on the specified session, with the given arguments.
 * @param session The target session on which to send the RPC message
 * @param target The name of the procedure to call
 * @param arguments The arguments to be sent in the message
 * @return
 */
public CompletableFuture<Map<String,Object>> invoke(WebSocketSession session, String target, Map<String, Object> arguments){
    Invocation invocationMessage = new Invocation(target, arguments);
    invocationMessage.setId(getNextId());

    // completeable future for the result. It does nothing, will be completed when reply is received which happen in a different thread, see completeInvocation
    CompletableFuture<Map<String, Object>> invocationFuture = new CompletableFuture<>();

    CompletableFuture<Void> senderFuture = sender.sendMessage(session, invocationMessage);

    // handle problem in the sending of the message
    senderFuture.exceptionally(e -> {
        // is this correct ??
        invocationFuture.completeExceptionally(e);
        return null;
    });

    // store the pending invocation in the registry
    registry.addPendingInvocation(new PendingInvocation(invocationMessage, session, invocationFuture));

    // return the future so the caller can have access to the result once it is ready
    return invocationFuture;
}
/**
*在指定会话上使用给定参数通过WS调用远程过程。
*@param session发送RPC消息的目标会话
*@param target要调用的过程的名称
*@param arguments消息中要发送的参数
*@返回
*/
公共CompletableFuture调用(WebSocketSession会话、字符串目标、映射参数){
调用调用消息=新调用(目标、参数);
调用message.setId(getNextId());
//结果的可完成未来。它不做任何事情,当在不同线程中收到回复时将完成,请参阅completeInvocation
CompletableFuture invocationFuture=新的CompletableFuture();
CompletableFuture senderFuture=sender.sendMessage(会话,调用消息);
//处理发送消息时出现的问题
发件人未来。例外(e->{
//这是正确的吗??
除(e)项外,完成;
返回null;
});
//将挂起的调用存储在注册表中
addPendingJournal(新的PendingJournal(调用消息、会话、调用未来));
//返回future,以便调用者可以在结果准备好后访问它
回归未来;
}

最简单的方法是使用
然后组合()
简单地链接期货:

//结果的可完成未来。它什么也不做,在收到回复时会完成,这发生在不同的线程中,请参阅completeInvocation
CompletableFuture invocationFuture=新的CompletableFuture();
CompletableFuture senderFuture=sender.sendMessage(会话,调用消息);
//将挂起的调用存储在注册表中
addPendingJournal(新的PendingJournal(调用消息、会话、调用未来));
//返回future,以便调用者可以在结果准备好后访问它
返回senderFuture.thencomose(\uuuu->invocationFuture);
如果异常完成了
发送者未来
,则返回的未来也将异常完成,
完成异常
将异常作为其原因(c.f.)

请注意,您可能还需要解决其他问题:

  • 如果出现异常,您不应该取消挂起的调用吗
  • 如果响应在调用
    addPendingJournal
    之前到达,会发生什么情况?您不应该在调用
    sendMessage
    之前调用它以避免出现问题吗
  • 既然您不使用
    invocationFuture
    做任何事情,那么在
    addpenidnginaction
    中创建它不是更好吗

嗯,我编写了一些测试并验证了行为,它实际上是按照我编写的方式工作的。
// completeable future for the result. It does nothing, will be completed when reply is received which happen in a different thread, see completeInvocation
CompletableFuture<Map<String, Object>> invocationFuture = new CompletableFuture<>();

CompletableFuture<Void> senderFuture = sender.sendMessage(session, invocationMessage);

// store the pending invocation in the registry
registry.addPendingInvocation(new PendingInvocation(invocationMessage, session, invocationFuture));

// return the future so the caller can have access to the result once it is ready
return senderFuture.thenCompose(__ -> invocationFuture);