【问题标题】:Return value directly from CompletableFuture.thenAccept直接从 CompletableFuture.thenAccept 返回值
【发布时间】:2017-04-19 07:30:57
【问题描述】:

我正在尝试从我的CompletableFuture 返回一个列表,如下所示:

public List<Provider> get() {
    CompletableFuture<List<Provider>> providersResponse = getSomeData();
    return providersResponse.thenAccept((List<Provider> providers) -> {
        return providers;
    });
}

它以“意外的返回类型失败。如何以异步方式返回结果?

【问题讨论】:

  • 请注意,您可以将参数写到thenAccept,这样更短更清晰:.thenAccept(providers -&gt; providers)
  • 那么get()的调用者应该如何处理虚假的异步List修改?
  • @Holger 你能详细说明一下吗?我不明白spurious asynchronous List modification 是什么。

标签: java concurrency java-8 completable-future


【解决方案1】:

您的目标存在根本矛盾。您只能让get() 返回一个完整的、直接可用的列表或“以异步方式返回结果”。

如果方法List&lt;Provider&gt; get()应该返回一个List,调用者可以不受限制地使用,它不能保持异步操作,因为当get()返回时操作必须已经完成。这可以像调用join()一样简单地实现,等待完成并得到结果:

public List<Provider> get() {
    CompletableFuture<List<Provider>> providersResponse = getSomeData();
    return providersResponse.join();
}

或者只是

public List<Provider> get() {
    return getSomeData().join();
}

这有效地将getSomeData() 的潜在异步操作转换为同步操作。

This answer,使用

public List<Provider> get() {
    List<Provider> providers = new ArrayList<>();
    CompletableFuture<List<Provider>> providersResponse = getSomeData();
    providersResponse.thenAccept(providers::addAll);
    return providers;
}

不等待操作完成,而是在调度addAll操作后返回一个新的ArrayList,其执行完全不受此方法的控制。

这是一个异步操作,所以当get() 返回时,List 可能仍然为空,但稍后会被任意线程更新。由于ArrayList 不是线程安全的,因此感知状态不必为空或已完成,它可能是任意中间状态,甚至不需要保持一致。使用该代码时,请为奇怪的异常、看似不可能的情况或其他意外情况做好准备。

当您使用线程安全的List 实现来修复该代码时,您仍然会遇到这样的问题,即返回的List 在查询时可能为空,并在调用者无法控制的任意时间点被填充。这是无法解决的,正如开头所说,这是想要异步操作但返回List的根本问题。

CompletableFuture 已经封装了一个潜在的异步操作,因此如果您希望操作保持异步,只需将CompletableFuture&lt;List&lt;Provider&gt;&gt; 返回给调用者,以获得控制权。否则,如果您希望调用者收到可以无限制使用的List,请等待完成后再返回结果,例如通过join()

【讨论】:

  • 我有一个类似的用例,但我是 CompletableFuture 的新手。我正在尝试将 AsyncTask [也 ExecutorService.submit() 替换为 .get()] 以将 CardViews 列表从 Room 数据库加载到 UI。您关于返回空列表的评论让我对使用 CompletableFuture 感到紧张。在此不胜感激:stackoverflow.com/questions/67294345/…
【解决方案2】:

试试这个:

public List<Provider> get() {
    List<Provider> providers = Collections.synchronizedList(new ArrayList<>());
    CompletableFuture<List<Provider>> providersResponse = getSomeData();
    providersResponse.thenAccept(providers::addAll);
    return providers;
}

【讨论】:

  • 这被破坏了,因为调用者可以获得一个空的ArrayList,另一个线程可能会在任意时间调用addAllArrayList 不是线程安全的。
  • 更新了答案。
  • 即使使用同步列表,调用者仍然很不方便,因为他无法知道列表何时会被填写——尤其是当getSomeData() 失败或返回一个空列表时。
【解决方案3】:

试试这个:

public List<Provider> get() {
    return getSomeData().get();
}

【讨论】:

  • CompletableFuture.get() 是阻塞的,所以它会锁定线程吗?
  • 您必须决定是否要阻止您的 get()。它可以是异步的并返回 CompletableFuture> 或阻塞并返回 List。它不能是异步的并返回一个普通的 List...
猜你喜欢
  • 2021-02-12
  • 2021-02-24
  • 1970-01-01
  • 1970-01-01
  • 2016-10-18
  • 1970-01-01
  • 2016-01-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多