0

下面的例子取自Spring 的 Getting Started Creating Asynchronous Methods。

@Service
public class GitHubLookupService {

  @Async
  public CompletableFuture<User> findUser(String user) throws InterruptedException {
    logger.info("Looking up " + user);
    String url = String.format("https://api.github.com/users/%s", user);
    User results = restTemplate.getForObject(url, User.class);
    // Artificial delay of 1s for demonstration purposes
    Thread.sleep(1000L);
    return CompletableFuture.completedFuture(results);
  }

}

AFAIK 我对计算机科学中异步方法的了解 -它应该立即返回。它应该是非阻塞的。

所以让我们在 Spring 的某个地方说我的代码findUser()是这样调用的:

CompletableFuture<User> user = service.findUser("foo");

这实际上会阻塞。它会阻塞服务上的另一个线程,Executor但由于 Thread.sleep(1000L). 正确的 ?

那么这个异步如何?

我的意思CompletableFuture是要获得对未来将要完成的计算的引用。但是在这里,当我回到完整的未来时,计算已经结束,即。我们正在使用 CompletableFuture.completedFuture(results).

那么CompletableFuture在这种情况下有什么意义呢?我的意思是,如果我要阻塞并仅在我的计算结束并且我得到结果时返回,我还不如只返回结果而不是 CompletableFuture.

这真的是非阻塞/异步的吗?

我在这里发现的唯一非阻塞方面是卸载到不同的线程,仅此而已。

我在某个地方出错了吗?我错过了什么?

谢谢。

4

2 回答 2

1

问题在于您创建Future. 您使用的代码是

CompletableFuture.completedFuture(results)

引用JavaDoc,这只是同步和异步之间的包装,其中计算是同步完成的:

返回已使用给定值完成的新 CompletableFuture。

这在您只想对某些输入进行异步工作的某些情况下很有用。考虑

(x) -> x==0 ? CompletableFuture.completedFuture(0) : CompletableFuture.supplyAsync(expensiveComputation)

我希望这可以使区别变得清晰-如果您想要真正的异步计算,则需要使用该supplyAsync函数:

返回一个CompletableFuture由在 中运行的任务异步完成的 new ,ForkJoinPool.commonPool()其值是通过调用给定的 获得的Supplier

于 2020-07-26T12:51:59.597 回答
1

您缺少的细节是,当@Async使用(并正确配置)时,将使用代理 bean,包装您的服务 bean。通过代理对异步方法的任何调用都将使用 SpringTaskExecutor来异步运行该方法。

将方法响应包装在同步FutureCompletableFuture.completedFuture是必要的,因此返回类型可以是Future. 但是,Future您返回的不是代理返回的。相反,代理返回由Future提供的a TaskExecutor,它将被异步处理。Future您通过 eg 创建的CompletableFuture.completedFuture由代理解包,其完成结果由代理返回Future

代理文档

我没有看到Spring 参考文档或Javadocs@Async中明确说明的所有上述代理细节。@EnableAsync但是,可以通过阅读所提供内容的字里行间来拼凑细节。

@AsyncJavadocs 顺便提到了服务代理,并解释了为什么在CompletableFuture.completedFuture服务方法的实现中使用:

从代理返回的Future句柄将是一个实际的异步Future,可用于跟踪异步方法执行的结果。但是,由于目标方法需要实现相同的签名,因此它必须返回一个临时Future句柄,该句柄只是传递一个值:例如 Spring's AsyncResult、 EJB 3.1'sAsyncResultCompletableFuture.completedFuture(Object)

@EnableAsync两个注释元素指定代理细节这一事实也使涉及代理的事实变得明显:modeproxyTargetClass.

问题示例

最后,将其应用于问题示例将使其具体化。findUser调用 bean上的方法的代码GitHubLookupService实际上将调用代理类上的方法,而不是直接调用GitHubLookupService实例上的方法。代理类的findUser方法向 Spring 提交一个任务TaskExecutor,并返回一个CompletableFuture将在提交的任务完成时异步完成。

提交的任务将调用findUser非代理中的实际方法GitHubLookupService。这将执行 REST 调用,休眠 1 秒,并返回一个已完成CompletableFuture的 REST 结果。

由于此任务发生在由 Spring 创建的单独线程中TaskExecutor,因此调用代码将立即继续执行GitHubLookupService.findUser调用,即使它至少需要 1 秒才能返回。

如果findUser在调用代码中使用了调用的结果(使用 eg CompletableFuture.get()),那么它将从中获得的值将与在代码中传递给Future的值相同。resultsCompletableFuture.completedFutureGitHubLookupService

于 2020-12-29T06:23:39.780 回答