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

时间:2017-07-11 09:09:02

标签: java spring completable-future

我有一个servlet请求,基本上请求输入日期给出的数据。由于我有多个日期,我必须发送多个请求,然后汇总结果。例如:

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
    }
}

问题:如何并行化上面的代码?表示:发送多个ServletReq异步,并将结果收集到列表中。等待所有请求完成(可能超时),并忽略SpecificException

我开始如下,但我不知道这是否是正确的方向,我也没有成功完全转移上面的代码。特别是关于要忽略的例外。

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();

到目前为止,但是:如何在异步结果上调用rsp.getResults(),并将所有内容放入list。如何在异步执行期间忽略SpecificException? (我无法修改webservice.send()方法!)。

2 个答案:

答案 0 :(得分:1)

  • 在供应商内捕获它们并返回,例如null。只有这样做才能做到这一点,无论如何你真的什么都不做。要在future.get()获得结果,您必须处理nullExecutionException s。

例如

CompletableFuture<ServletRsp> future = CompletableFuture.supplyAsync(() -> {
    try {
        return webservice.send(new ServletReq(date));
    } catch (SpecificException e) {
        return null;
    }
});
  • 将它们重新抛出为(自定义?)RuntimeException,这样您就不会丢失它们。现在你最后只处理异常,但有些是双重包装。
  • 手动完成未来。

E.g。

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

ExecutionException之外不再包装。 CompletableFuture.supplyAsync完全相同,但没有代码来处理已检查的异常。

  • 只需使用接受抛出代码的旧的ExecutorService#submit(Callable<T> callable)方法:

e.g。

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

答案 1 :(得分:0)

我认为你在那里走的很好。

问题是,没有机制可以很好地收集结果,除非自己动手:

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

我已经尝试保留大部分代码,因为您已经写好了。也许它对流有点干净了:

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.
    });