CompletableFuture:异步调用void函数

时间:2018-03-13 02:51:32

标签: java runnable callable completable-future

我正在尝试使用针对某些数据库异常的重试策略来实现数据库查询。重试策略的代码不是很相关,所以我没有包含它。正如您在下面的代码中看到的那样 - 我编写了一个retryCallable,它采用了重试策略和populateData()中的Callable。

getDataFromDB中,我从数据库中获取数据,并将数据放在一个全局散列图中,该散列图充当应用程序级别的缓存。

此代码按预期工作。我想从另一个班级调用populateData。但是,这将是一个阻止呼叫。由于这是数据库并且具有重试策略,因此这可能很慢。我想异步调用populateData

如何使用CompletableFuture或FutureTask来实现这一目标? CompletableFuture.runAsync期待一个可运行的。 CompletableFuture.supplyAsync期待供应商。我以前没有实现过这些东西。所以关于最佳实践的任何建议都会有所帮助。

Class TestCallableRetry {

public void populateData() {
        final Callable<Set<String>> retryCallable = new RetryingCallable<>(retryStrategyToRetryOnDBException(), getDataFromDB());
        Set<String> data = new HashSet<>();

        data = retryCallable.call();

        if (data != null && !data.isEmpty()) {
            // store data in a global hash map
        }
    }

    private Callable<Set<Building>> getDataFromDB() {
        return new Callable<Set<String>>() {
            @Override
            public Set<String> call() {
                // returns data from database
            }
        };
    }
}

Class InvokeCallableAsynchronously {
    public void putDataInGlobalMap {
      // call populateData asynchronously
    }
}

3 个答案:

答案 0 :(得分:2)

您在CompletableFuture中结合了多种实用方法,并且值得探索所有这些方法。

让我们从populateData方法开始。根据其名称,您可以推断它应该接受来自某个地方的数据流。

它的签名可能如下所示:

void populateData ( Supplier<? extends Collection<Building> dataSupplier );

Supplier,顾名思义,它只是为我们提供了一些数据。

getDataFromDB()似乎适合担任Supplier角色。

private Set<Building> getDataFromDB() // supply a building's collection

我们希望populateData执行asynchronously并返回结果,无论操作是否正确执行。

因此,将来populateData可能会返回,并告诉我们事情的进展情况。

让我们将签名转换为:

CompletableFuture<Result> populateData(Supplier<? extends Collection<Building>> supplier);

现在让我们看一下方法体的外观:

CompletableFuture<Result> populateData(Supplier<? extends Collection<Building>> supplier) {
    return CompletableFuture                // create new completable future from factory method
            .supplyAsync(supplier)          // execute the supplier method (getDataFromDB() in our case) 
            .thenApplyAsync(data -> {       // here we can work on the data supplied
                if (data == null || data.isEmpty()) return new Result(false);
                // some heavy operations
                for (Building building : data) {
                    // do something
                }

                return new Result(true); // return dummy positive result data            
             })
             .handleAsync((result, throwable) -> {
                // check if there was any exception
                if (throwable != null) {
                    // check if exception was thrown
                    Log.log(throwable);
                    return new Result(false);
                }
                return result;
            });
}

现在我们可以从某处调用populateData,并在异步完成执行时应用另一个回调来执行。

populateData(TestCallableRetry::getDataFromDB).thenAccept( result -> {
        if ( ! result.success ) {
            // things went bad... retry ??
        }
    });

现在,这取决于您希望如何应用重试策略。如果您只想重试一次,则可以在populateData内再次拨打thenAcceptAsync

您还应该在供应商方法中catch例外,并将其转换为java.util.concurrent.CompletionException,因为它们会在CompletableFuture内顺利处理。

答案 1 :(得分:1)

如果您将populateData方法拆分为两个部分,一个Supplier用于获取数据,另一个Consumer用于存储数据,将很容易用{{1}链接它们}}

CompletableFuture

然后,在// Signature compatible with Supplier<Set<String>> private Set<String> fetchDataWithRetry() { final RetryingCallable<Set<String>> retryCallable = new RetryingCallable<>(retryStrategyToRetryOnDBException(), getDataFromDB()); try { return retryCallable.call(); } catch (Exception e) { log.error("Call to database failed", e); return Collections.emptySet(); } } // Signature compatible with Consumer<Set<String>> private void storeData(Set<String> data) { if (!data.isEmpty()) { // store data in a global hash map } }

populateData()

使用private ExecutorService executor = Executors.newCachedThreadPool(); public void populateData() { CompletableFuture .supplyAsync(this::fetchDataWithRetry, executor) .thenAccept(this::storeData); } 版本supplyAsync是可选的。如果您使用单个arg版本,您的任务将在公共池中运行;对于短期运行任务可以,但对于阻止的任务则不行。

答案 2 :(得分:0)

它非常简单,因为java8只是使用 CompletableFuture.runAsync(() -> object.func());