如何在vert.x中阻塞线程以等待响应?

9

我有这样的情况:我调用一个外部API A,并使用其响应来发送请求到API B并调用它,然后将响应返回给API A的调用者。就像下面这样:

   method(){
    response = call API A
    }

    method_for_API_A(){
      handler() ->{
      API_B
      }
    return response;
    }

    method_for_API_B(){
    //code to call API B
    }

我面临的问题是API A方法在未等待来自B的响应即返回了响应。
我查看了vert.x的executeBlocking方法,还尝试使用“阻塞队列”,但无法达到预期的效果。请问是否有人能指导我正确的做法。先谢谢了。
编辑:为了解释具体情况。
Class MyClass{
 public Response method_A (Request request){
 String respFromApiA = Call_API_A(request) ;  // STEP 1
 Response respFromApiB = Call_API_B(request, respFromApiA); // STEP 2
 Print(respFromApiB)   // PRINT FINAL Response
 return respFromApiB; // STEP 3
}

String Call_API_A(Request request){
// Implementation
Print(string);  // PRINT API A response
return string
}

Response Call_API_B(Response response){
// Implementation
Print(response);  // PRINT API B response
return response;
}

}

我正在使用Java的vert.x框架。 在执行过程中,流程到达STEP 1,启动API A调用。然后转到STEP 2(不等待'respFromApiA'),并调用API B(由于'respFromApiA'为空而最终失败)。最后,流程到达STEP 3并从此处返回(而不等待API A和API B的结果)。 如果我们查看打印顺序,它将类似于这样:

PRINT FINAL Response
PRINT API A response
PRINT API B response

我想要达到什么目的?
Wait for API A response.
Make call to API B. Wait for API B response.
Return response got from API B.

我希望这次我能让您明白。如果您需要更多的输入,请告诉我。


为什么不在“方法”中依次执行这两个调用呢? - Fildor
我猜你正在进行非阻塞调用。它们很可能会在回调中返回响应(抱歉,我不了解vert.x)。因此,你需要找到一种告诉vert.x你想要这些调用是阻塞的并返回结果的可能性,或者你需要使用回调函数。 - Fildor
也许如果你展示更多的代码(真实的代码),那么有经验的Vert.x开发者可能会提供帮助... - Fildor
3个回答

19

Vert.x是高度异步的。大多数操作实际上会立即返回,但它们的结果将在稍后的时间点对Handler可用。到目前为止还好。如果我理解得正确,那么您需要在AHandler中调用B。在这种情况下,A需要完成并且结果可用于在调用B之前使用:

callA(asyncResultA -> {
  System.out.println("Result A: " + asyncResultA.result());

  callB(asyncResultB -> {
    System.out.println("Result B:" + asyncResultB.result());
  });
});

但是你试图做的是将异步变成同步。你不能也不应该尝试使异步结果在主程序流中可用——这是行不通的。

String respFromApiA = Call_API_A(request); // STEP 1
Response respFromApiB = Call_API_B(request, respFromApiA); // STEP 2
Print(respFromApiB); // PRINT FINAL Response
return respFromApiB; // STEP 3
Call_API_A无法直接返回结果,因为它是异步计算的。结果仅对Call_API_AHandler可用(请参见上面的示例)。Call_API_B同样如此–因此您不能返回Call_API_B的结果。您的类的调用者还需要使用Handler调用您的类。

现在有一些额外的信息。在您的情况下,多个异步结果彼此依赖。Vert.x提供了一种更方便的处理异步结果的方法–所谓的FuturesFuture(有时称为Promise,但在Java世界中称为Future)是异步调用结果的占位符。在文档中阅读有关它们的信息。

有了Future,您可以像这样做:

Future<...> callAFuture = Future.future();
callA(asyncResultA -> {
  if (asyncResultA.succeeded()) {
    System.out.println("A finished!");
    callAFuture.complete(asyncResultA.result());
  } else {
    callAFuture.fail(asyncResultA.cause());
  }
});

因此,您不应该试图以同步方式返回B的异步结果,而应该返回一个Future,这样您类的调用方就可以注册AB的异步结果。

希望这可以帮到您。

编辑:将Future作为返回值

假设您想使用Future包装callA以便可以使用它,请按照以下方式操作:

public Future<String> doSomethingAsync() {
  Future<String> callAFuture = Future.future();

  // do the async stuff
  callA(asyncResultA -> {
    if (asyncResultA.succeeded()) {
      System.out.println("A finished!");
      callAFuture.complete(asyncResultA.result());
    } else {
      callAFuture.fail(asyncResultA.cause());
    }
  });

  // return Future with the asyncResult of callA
  return callAFuture;
}

调用此函数的代码可以像这样使用 Future:

Future<String> doSomethingFuture = doSomethingAsync();
doSomethingFuture.setHandler(somethingResult -> {
  // ... doSomethingAsync finished
});

如果您想并发地执行多个Future,但它们彼此之间没有依赖关系,那么也可以进行组合:

CompositeFuture.all(futureA, futureB).setHandler(connections -> {
  // both Futures completed
});
如果您在像Vert.x这样的异步环境中工作,大部分时间会使用即将可用的结果(也称为Future)。在Future的Handler中,通常会进行另一个异步调用。您可以像callA的Handler中的callB示例那样,使用一个Future包装另一个Future。
如何将Future的异步结果作为HTTP响应返回?就像这样:
router.route("/").handler(routingContext -> {
  HttpServerResponse response = routingContext.response();

  Future<String> future = doSomethingAsync();
  future.setHandler(somethingResult -> {
    if (somethingResult.succeeded()) {
      response
        .end(somethingResult.result());
    } else {
      routingContext.fail(500);
    }
  });
});

感谢@Alexvetter给我答复。我同意在API_A的处理程序中调用API_B的逻辑。我已经尝试过这种方法,也成功了。我犯的错误是,试图同步地将响应返回给类的调用者,而应该通过处理程序进行处理。只是为了澄清一点,在最后我必须将API_B的响应返回给调用者。你提到了使用Future对象,那么我们只需要将返回类型设为Future,然后将Future对象返回给调用者以获得响应? - tausif
没错,你应该使用 Future 及其方法 setHandler 试一试。 - alexvetter
由于Vert.x高度异步化,您的应用程序将会传递'Futures'并在需要时组合它们。重要的是不要阻塞事件循环! - alexvetter
@tausif 添加了一些更多的例子,希望有所帮助。 - alexvetter
那真的很有帮助。非常感谢您描述得如此恰当。 - tausif
显示剩余2条评论

5

我曾使用Future来返回一些结果,以便在其他方法中再次使用它。这是我的实现,希望能对某些人有所帮助:

 public static void ussdMessages(RoutingContext routingContext){
    String codeService = routingContext.getBodyAsJson().getString("codeService");
    Future<String> futureQuery=getServiceQuery(codeService);
    Future<JsonObject> futureParams = getServiceParams(codeService);
    CompositeFuture.all(futureQuery,futureParams).setHandler(r->{
        System.out.println(futureQuery.result());
        System.out.println(futureParams.result());
    });

}

public static Future<JsonObject> getServiceParams(String codeService){
    Future<JsonObject> future=Future.future();
    JsonObject params = new JsonObject();
    params.put("QUERY", Queries.DB_SELECT_SERVICE_PARAMS);
    params.put("PARAMS", new JsonArray().add(codeService));
    DB.select(params, res -> {
        if (res.succeeded()) {
            future.complete(res.result());
        } else {
            future.fail(res.cause().getMessage());
        }
    });
    return future;
}


public  static Future<String>  getServiceQuery(String codeService){
    Future<String> future = Future.future();
    JsonObject params = new JsonObject();
    params.put("QUERY", Queries.DB_SELECT_SERVICE_QUERY);
    params.put("PARAMS", new JsonArray().add(codeService));
    System.out.println(params);
    DB.select(params, res -> {
        if (res.succeeded()) {
          // query = res.result().getJsonArray("results").getJsonArray(0).getString(0);
            future.complete(res.result().getJsonArray("results").getJsonArray(0).getString(0));
        } else {
            future.fail(res.cause().getMessage());
        }
    });
    return future;
}

当然有帮助!- 谢谢 - Juan Ricardo

0

你有三个选择:

  1. 进行API A调用,在回调函数中进行API B调用。
  2. 使用你选择的异步框架(https://spring.io/guides/gs/async-method/),可以并行运行两个API调用。
  3. 与第一种方法相同,但使用Promise。

第二种方法是最佳解决方案,因为它将显着提高速度,并且你可以轻松添加API C。


据我理解这个问题,从调用B的结果中需要一个参数来调用A。因此并行调用并没有实际意义。 - Fildor
我理解A和B的输出应该要做一些事情,也许是我的问题。 - Martin Gottweis
我是在提到问题的这一部分:“我调用一个外部API A,并使用其响应来作为API B请求的输入”。也许@tausif可以表达得更清楚一点。 - Fildor
是的,可能B需要一个回调函数,并且A中的返回语句需要在B的回调函数中。 - Martin Gottweis
嗨@Fildor,请检查我的问题的编辑部分。 - tausif

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接