CompletableFuture join()调用挂起主线程。个别期货永远不会完成

时间:2019-09-24 16:42:57

标签: java completable-future

我正在编写一个创建多个(7)CompletableFutures的函数。这些期货基本上都做两件事:

  1. 使用supplyAsync()从某些数据库中获取数据
  2. 使用thenAccept(),将此数据写入CSV文件

当7个期货全部完成后,我想继续执行代码。因此,我使用allOf(),然后在allOf()返回的Void CompletableFuture上调用join()。

问题在于,即使在执行完所有期货之后(我可以看到生成了CSV),join()调用仍会卡住,并且进一步的代码执行将永远被阻止。

我尝试了以下操作:

  1. 在每个未来之后一个接一个的调用join()来等待每个未来。这有效,但是以并发为代价。我不想这样做。

  2. 尝试将get()与TIMEOUT而不是join()一起使用。但是,这总是会引发异常(因为总是超时),这是不希望的。

  3. 看到此JDK错误:https://bugs.openjdk.java.net/browse/JDK-8200347。不知道这是否是一个类似的问题。

  4. 尝试在没有join()或get()的情况下运行,这将无法保持线程执行,并且再次执行是不希望的。

创建所有期货的主要功能。

public CustomResponse process() {
        CustomResponse msgResponse = new CustomResponse();
        try {
            // 1. DbCall 1
            CompletableFuture<Void> f1 = dataHelper.fetchAndUploadCSV1();

            // 2. DbCall 2
            CompletableFuture<Void> f2 = dataHelper.fetchAndUploadCSV2();


            // 3. DbCall 3
            CompletableFuture<Void> f3 = dataHelper.fetchAndUploadCSV3();


            // 4. DbCall 4
            CompletableFuture<Void> f4 = dataHelper.fetchAndUploadCSV4();


            // 5. DbCall 5
            CompletableFuture<Void> f5 = dataHelper.fetchAndUploadCSV5();


            // 6. DbCall 6
            CompletableFuture<Void> f6 = dataHelper.fetchAndUploadCSV6();


            // 7. DbCall 7
            CompletableFuture<Void> f7 = dataHelper.fetchAndUploadCSV7();


            CompletableFuture<Void>[] fAll = new CompletableFuture[] {f1, f2, f3, f4, f5, f6, f7};


            CompletableFuture.allOf(fAll).join();
            msgResponse.setProcessed(true);
            msgResponse.setMessageStatus("message");
        } catch (Exception e) {
    msgResponse.setMessageStatus(ERROR);
            msgResponse.setErrorMessage("error");
        }
        return msgResponse;
    }

每个fetchAndUploadCSV()函数如下:

public CompletableFuture<Void> fetchAndUploadCSV1() {
        return CompletableFuture.supplyAsync(() -> {
            try {
                return someService().getAllData1();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }).thenAccept(results -> {
            try {
                if (results.size() > 0) {
                    csvWriter.uploadAsCsv(results);
                }
                else {
                    log.info(" No data found..");
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }

这就是csvWriter.uploadAsCsv(results)的样子-

public <T> void uploadAsCsv(List<T> objectList) throws Exception {
        long objListSize = ((objectList==null) ? 0 : objectList.size());
        log.info("Action=Start, objectListSize=" + objListSize);
        ByteArrayInputStream inputStream = getCsvAsInputStream(objectList);
        Info fileInfo = someClient.uploadFile(inputStream);
        log.info("Action=Done, FileInfo=" + ((fileInfo==null ? null : fileInfo.getID())));
    }

我在这里使用OpenCSV将数据转换为CSV流。而且我总是可以看到最后一个日志行。

预期结果: 所有获取的数据,生成的CSV和CustomResponse都应返回已处理的状态,而不会出现错误消息。

实际结果: 获取所有数据,生成CSV并挂起主线程。

1 个答案:

答案 0 :(得分:1)

您可以在每个创建的join上使用CompletableFuture,而不会牺牲并发性:

public CustomResponse process() {
    CustomResponse msgResponse = new CustomResponse();

    List<CompletableFuture<Void>> futures = Arrays.asList(dataHelper.fetchAndUploadCSV1(),
            dataHelper.fetchAndUploadCSV2(),
            dataHelper.fetchAndUploadCSV3(),
            dataHelper.fetchAndUploadCSV4(),
            dataHelper.fetchAndUploadCSV5(),
            dataHelper.fetchAndUploadCSV6(),
            dataHelper.fetchAndUploadCSV7());

    return CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0]))
            .thenApply(v -> {
                msgResponse.setProcessed(true);
                msgResponse.setMessageStatus("message");
                return msgResponse;
            })
            .exceptionally(throwable -> {
                msgResponse.setMessageStatus("ERROR");
                msgResponse.setErrorMessage("error");
                return msgResponse;
            }).join();
}

allOf返回一个新的CompletableFuture,当所有给定的CompletableFutures完成时,新的join完成。因此,在thenApply中调用exceptionally时,它将立即返回。本质上,已经完成的期货正在加入。这样消除了阻塞。另外,为处理可能的异常,应调用/** * Convert date of birth into age * param {string} dateOfBirth - date of birth * param {string} dateToCalculate - date to compare * returns {number} - age */ function getAge(dateOfBirth, dateToCalculate) { const dob = moment(dateOfBirth); return moment(dateToCalculate).diff(dob, 'years'); };