简体   繁体   English

如何使用CompletableFuture并行化Web服务请求?

[英]How to parallelize webservice requests with CompletableFuture?

I have a servlet request that basically requests data given by an input date. 我有一个Servlet请求,该请求基本上请求输入日期给出的数据。 As I have multiple dates, I have to send multiple requests, and then aggregate the results. 由于我有多个日期,因此我必须发送多个请求,然后汇总结果。 For example: 例如:

List<Result> results = new ArrayList<>();

for (LocalDate date : dates) {
    ServletReq req = new ServletReq(date);

    try {
        ServletRsp rsp = webservice.send(req);
        results.addAll(rsp.getResults());
    } catch (SpecificException e) {
        //just ignore this result and continue
    }
}

Question: how can I parallelize the code above? 问题:如何并行化上面的代码? Means: sending multiple ServletReq async, and collect the result into the list. 意思是:发送多个ServletReq异步,并将结果收集到列表中。 Wait for all requests to finish (maybe with a timeout), and ignore the SpecificException . 等待所有请求完成(可能超时),然后忽略SpecificException

I started as follows, but neither do I know if this is the right direction, nor did I succeed transfering the code above completely. 我的开始如下,但是我既不知道这是正确的方向,也没有成功地完全转移上面的代码。 Especially regarding the exception to be ignored. 尤其是对于要忽略的异常。

ExecutorService service = Executors.newCachedThreadPool();
List<CompletableFuture<ServletRsp>> futures = new ArrayList<>();

for (LocalDate date : dates) {
    ServletReq req = new ServletReq(date);
    CompletableFuture future = CompletableFuture.supplyAsync(() -> webservice.send(req), service);
    futures.add(future);
}

CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).join();

So far, but: How can I call rsp.getResults() on the async result, and put everything into the list . 到目前为止,但是:我如何在异步结果上调用rsp.getResults() ,并将所有内容放入list And how can I ignore the SpecificException during the async execution? 在异步执行过程中如何忽略SpecificException (I cannot modify the webservice.send() method!). (我无法修改webservice.send()方法!)。

  • catch them within the supplier and return eg null . 在供应商内捕获它们并返回,例如null Only do that if you'd really do nothing with the exception anyways. 如果您确实对例外情况一无所知,则只能这样做。 To get the results at future.get() you have to deal with null and ExecutionException s. 要在future.get()获得结果,您必须处理nullExecutionException

Eg 例如

CompletableFuture<ServletRsp> future = CompletableFuture.supplyAsync(() -> {
    try {
        return webservice.send(new ServletReq(date));
    } catch (SpecificException e) {
        return null;
    }
});
  • rethrow them as (custom?) RuntimeException so you don't lose them. 将它们作为(自定义?) RuntimeException重新抛出,这样就不会丢失它们。 Now you deal with just exceptions in the end but some are double-wrapped. 现在,您最终只处理异常,但有些异常是双重包装的。
  • Manually complete the future. 手动完成未来。

Eg 例如

CompletableFuture<ServletRsp> future = new CompletableFuture<>();
service.execute(() -> {
    try {
        future.complete(webservice.send(new ServletReq(date));
    } catch (SpecificException e) {
        future.completeExceptionally(e);
    }
});
futures.add(future);

No more wrapping besides in ExecutionException . 除了ExecutionException之外,没有其他包装。 CompletableFuture.supplyAsync does about exactly that, but has no code to deal with checked exceptions. CompletableFuture.supplyAsync做到这一点,但是没有代码可以处理已检查的异常。

  • Just use the good old ExecutorService#submit(Callable<T> callable) method which accepts code that throws: 只需使用旧的ExecutorService#submit(Callable<T> callable)方法即可,该方法接受抛出的代码:

eg 例如

List<Callable<String>> tasks = dates.stream()
        .map(d -> (Callable<ServletRsp>) () -> send(new ServletReq(d)))
        .collect(Collectors.toList());
List<Future<ServletRsp>> completed = service.invokeAll(tasks);

I think you're on a good path there. 我认为您在那儿走的很好。

The issue is, that there is no mechanism to nicely collect the results, except doing it yourself: 问题是,除了自己做之外,没有机制可以很好地收集结果:

ExecutorService service = Executors.newCachedThreadPool();
List<CompletableFuture<Void>> futures = new ArrayList<>(); // these are only references to tell you when the request finishes 
Queue<ServletRsp> results = new ConcurrentLinkedQueue<>(); // this has to be thread-safe

for (LocalDate date : dates) {
    ServletReq req = new ServletReq(date);
    CompletableFuture future = CompletableFuture
            .supplyAsync(() -> webservice.send(req), service)
            .thenAcceptAsync(results::add);
    futures.add(future);
}

CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).join();
// do stuff with results

I've tried to keep most of the code as you've written it. 在您编写代码时,我尝试保留大多数代码。 Maybe it's a bit cleaner with streams: 也许使用流更干净:

List<CompletableFuture<Void>> collect = dates
        .map(date -> CompletableFuture
            .supplyAsync(() -> webservice.send(new ServletReq(date)), service)
            .thenAcceptAsync(results::add))
        .collect(Collectors.toList());

    // wait for all requests to finish
    CompletableFuture.allOf(collect.toArray(new CompletableFuture[collect.size()])).thenAcceptAsync(ignored -> {
        //you can also handle the response async.
    });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM