同时运行一个Void CompletionStage,但忽略结果

huangapple go评论85阅读模式
英文:

Concurrently run a Void CompletionStage but ignore result

问题

以下是您要翻译的内容:

我有两个completionStages方法调用,每个调用远程服务,如果不满足条件。它们都是相当长时间的处理过程,我们需要减少延迟。我也不关心secondFuture的响应。它可以返回CompletionStage<Void>,因为我只关心该方法是否在我们退出主方法之前运行。另一个复杂性是injectedClass2.serviceCall还会抛出一个非常重要的异常(404 StatusRuntimeException),需要将其传递给客户端。

我如何确保第一个和第二个future异步运行(彼此不依赖),同时第二个future会将其错误代码和异常传递给客户端。

下面是我对此的最佳尝试。它可以工作,但我希望学习一种更好的实现方式,以充分利用completables/streams等。

try {
    .
    .
    .
    CompletionStage<Response> firstFuture;
    CompletionStage<Response> secondFuture = CompletableFuture.completedFuture(Response.default());
    if (condition) {
        firstFuture = legacyImplThing.resolve(param1, param2);
    } else {
        firstFuture =
            injectedClass1.longRunningOp(param1, param2);
        secondFuture = injectedClass2.serviceCall(param1, param2, someOtherData);
    }

    final CompletionStage<MainMethodResponse> response =
        CompletableFutures.combine(firstFuture, secondFuture, (a, b) -> a)
            .thenApply(
                v -> ServiceResponse.newBuilder().setParam(v.toString()).build());

    handleResponse(response, responseObserver);
} catch (Exception e) {
    responseObserver.onError(e);
}

也许超出范围,如何测试/检查两个completionStages是否并发运行?

编辑:CompletableFutures.combine()是第三方库方法,不是java.util.concurrent包的一部分。

英文:

I have two completionStages method calls that each call a remote service if a condition is not met. They are both pretty long running processes and we need to decrease latency. I also do not care for the secondFuture's response. It could return CompletionStage&lt;Void&gt; as I only care if the method runs before we exit the main method. An added complexity is that injectedClass2.serviceCall also throws a really important exception (404 StatusRuntimeException) that needs to be surfaced to the client.

How do I ensure that the first and second future run asynchronously (not dependant on each other) meanwhile the second future surfaces its error codes and exceptions for the client.

Main Method below is my best attempt at this. It works, but I am looking to learn a better implementation that takes advantage of completables/streams,etc.

try {
      .
      .
      .
      CompletionStage&lt;Response&gt; firstFuture;
      CompletionStage&lt;Response&gt; secondFuture = CompletableFuture.completedFuture(Response.default());
      if (condition) {
        firstFuture = legacyImplThing.resolve(param1, param2);
      } else {
        firstFuture =
            injectedClass1.longRunningOp(param1, param2);
        secondFuture = injectedClass2.serviceCall(param1, param2, someOtherData);
      }

      final CompletionStage&lt;MainMethodResponse&gt; response =
          CompletableFutures.combine(firstFuture, secondFuture, (a, b) -&gt; a)
              .thenApply(
                  v -&gt; ServiceResponse.newBuilder().setParam(v.toString()).build());

      handleResponse(response, responseObserver);
    } catch (Exception e) {
      responseObserver.onError(e);
    }

Maybe out of scope, how would one test/check that two completionStages were run concurrently?

EDIT: CompletableFutures.combine() is a third-party library method and not part of the java.util.concurrent package.

答案1

得分: 3

链接其他阶段不会改变先前的阶段。换句话说,并行性完全不受您的控制,因为它已经确定下来了。

更具体地说,当您调用 injectedClass1.longRunningOp(param1, param2) 时,方法 longRunningOp 的实现决定了返回的 future 如何完成。同样,当您调用 injectedClass2.serviceCall(param1, param2, someOtherData) 时,serviceCall 的实现将决定返回的 future 的完成方式。这两个方法可能在幕后使用相同的执行器,也可能采用完全不同的方法。

唯一能够影响并行性的情况是,两个方法都在调用者的线程中执行实际操作,最终返回一个已经完成的 future。在这种情况下,您需要将每个调用包装到另一个异步操作中,以便让它们并行运行。但是,如果在调用者的线程中执行耗时操作时返回一个 future,这将是一种奇怪的设计。

您的代码

CompletableFutures.combine(firstFuture, secondFuture, (a, b) -&gt; a)

文档中记录的 API 不符。有效的调用应该是

firstFuture.thenCombine(secondFuture, (a, b) -&gt; a)

在这种情况下,您不会影响 firstFuturesecondFuture 的完成。您只是在两个 future 都完成后指定接下来应该发生什么。

顺便提一下,没有理由在 thenCombine 中指定像 (a, b) -&gt; a 这样的微不足道的函数,然后再链接另一个 thenApply。您可以一开始就使用

firstFuture.thenCombine(secondFuture,
    (v, b) -&gt; ServiceResponse.newBuilder().setParam(v.toString()).build())

来代替。

英文:

Chaining other stages does not alter the previous stages. In other words, the parallelism is entirely outside your control, as it has been determined already.

More specifically, when you invoke injectedClass1.longRunningOp(param1, param2), the implementation of the method longRunningOp decides, how the returned future will be completed. Likewise, when you call injectedClass2.serviceCall(param1, param2, someOtherData), the implementation of serviceCall will determine the returned future’s completion. Both methods could use the same executor behind the scenes or entirely different approaches.

The only scenario where you can influence the parallelism, is that both methods perform the actual operation in the caller’s thread, to eventually return an already completed future. In this case, you would have to wrap each call into another asynchronous operation to let them run in parallel. But it would be a strange design to return a future when performing a lengthy operation in the caller’s thread.

Your code

CompletableFutures.combine(firstFuture, secondFuture, (a, b) -&gt; a)

does not match the documented API. A valid call would be

firstFuture.thenCombine(secondFuture, (a, b) -&gt; a)

In this case, you are not influencing the completions of firstFuture or secondFuture. You are only specifying what should happen after both futures have been completed.

There is, by the way, no reason to specify a trivial function like (a, b) -&gt; a in thenCombine, just to chain another thenApply. You can use

firstFuture.thenCombine(secondFuture,
    (v, b) -&gt; ServiceResponse.newBuilder().setParam(v.toString()).build())

in the first place.

huangapple
  • 本文由 发表于 2020年5月5日 11:44:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/61605397.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定