有没有办法将CompletableFuture放入循环?

时间:2020-10-21 19:05:00

标签: java java-11 java-http-client

以下代码的问题是我必须等待所有三个任务完成。

如果第一个和第二个任务在200毫秒内完成,第三个任务在2秒钟内完成,那么我将需要等待2秒钟才能加载下三个URL。

理想情况下,我会在每个任务完成后立即发送一个新请求,并以某种方式延迟主线程,直到ArrayList为空。

简单来说,我希望每个可完成的未来都以一种由旧任务完成触发的循环运行。

(我经常使用事件在JavaScript中执行此操作)

有人能想到我将如何实现这一目标吗?

    private static void httpClientExample(){

    ArrayList<String> urls = new ArrayList<>(
            Arrays.asList(
                    "https://www.bing.com/",
                    "https://openjdk.java.net/",
                    "https://openjdk.java.net/",
                    "https://google.com/",
                    "https://github.com/",
                    "https://stackoverflow.com/"
            ));

    HttpClient httpClient = HttpClient.newHttpClient();

    var task1 = httpClient.sendAsync(HttpRequest.newBuilder()
            .uri(URI.create(urls.get(0)))
            .build(), HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::uri).thenAccept(System.out::println);

    var task2 = httpClient.sendAsync(HttpRequest.newBuilder()
            .uri(URI.create(urls.get(1)))
            .build(), HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::uri).thenAccept(System.out::println);

    var task3 = httpClient.sendAsync(HttpRequest.newBuilder()
            .uri(URI.create(urls.get(2)))
            .build(), HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::uri).thenAccept(System.out::println);

    // All tasks have to complete
    var all = CompletableFuture.allOf(task1, task2, task3).join();
    
    // Get the next 3 URLs

    System.out.println("Main Thread Completed");
}

2 个答案:

答案 0 :(得分:4)

让作业本身删除另一个待处理的URL并提交它,将需要线程安全队列。

让主线程执行操作可能会更容易,例如喜欢

var httpClient = HttpClient.newHttpClient();
var pending = new ArrayDeque<CompletableFuture<?>>(3);
for(String url: urls) {
    while(pending.size() >= 3 && !pending.removeIf(CompletableFuture::isDone))
        CompletableFuture.anyOf(pending.toArray(CompletableFuture<?>[]::new)).join();

    pending.addLast(httpClient.sendAsync(HttpRequest.newBuilder()
            .uri(URI.create(url))
            .build(), HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::uri).thenAccept(System.out::println));
}
CompletableFuture.allOf(pending.toArray(CompletableFuture<?>[]::new)).join();

这将等待,直到提交的三个作业中的至少一个完成(使用anyOf / join),然后再提交下一个作业。循环结束时,最多可能有三个仍在运行的作业。循环之后的后续allOf / join将等待这些作业的完成,因此所有作业均在此之后完成。当您希望启动程序线程在已知所有作业都已提交而无需等待完成的情况下继续执行时,只需删除最后一条语句即可。

答案 1 :(得分:1)

如果您对并行调用的最大数量没有要求,那么事情会变得容易得多:

private static void httpClientExample() throws Exception {

  final ArrayList<String> urls = ...; //list of urls 

  final HttpClient httpClient = HttpClient.newBuilder().executor(
                                    Executors.newFixedThreadPool(10)).build();

  final List<CompletableFuture<Void>> allFutures = new ArrayList<>();
  for (String url : urls) {
    final CompletableFuture<Void> completableFuture = httpClient
        .sendAsync(HttpRequest.newBuilder().uri(URI.create(url)).build(),
            HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::uri).thenAccept(System.out::println);
    allFutures.add(completableFuture);
  }

  CompletableFuture.allOf(allFutures.toArray(CompletableFuture[]::new)).get();
}