Spring 如何启动 @Async 方法实际上 async/non-blocking?

How are Spring boot @Async methods actually async/non-blocking?

以下例子摘自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);
  }

}

据我所知,我对计算机科学中异步方法的了解 - 它应该立即 return。它应该是非阻塞的。

所以让我们说在 Spring 中的某处让我的代码 findUser() 被这样调用:

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

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

那么异步如何?

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

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

这真的是怎样 non-blocking/async ?

我在这里发现的唯一非阻塞方面是 卸载到不同的线程,没有别的。

我是不是哪里出错了?我错过了什么?

谢谢。

问题在于您创建 Future 的方式。您使用的代码是

CompletableFuture.completedFuture(results)

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

Returns a new CompletableFuture that is already completed with the given value.

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

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

我希望这能说明区别 - 如果你想要真正的异步计算,你需要使用 supplyAsync 函数:

Returns a new CompletableFuture that is asynchronously completed by a task running in the ForkJoinPool.commonPool() with the value obtained by calling the given Supplier.

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

将方法响应包装在同步 Future 中是必要的,例如 CompletableFuture.completedFuture,因此 return 类型可以是 Future。但是,Future 你 return 不是代理 return 编辑的那个。相反,代理 return 是 TaskExecutor 提供的 Future,它将被异步处理。您通过例如创建的 Future CompletableFuture.completedFuture 由代理解包,其完成结果由代理的 Future 编辑return。

代理文档

我没有在 Spring reference documentation or in the @Async or @EnableAsync Javadoc 中看到所有上述代理详细信息。但是,可以通过阅读所提供内容的字里行间来拼凑细节。

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

A Future handle returned from the proxy will be an actual asynchronous Future that can be used to track the result of the asynchronous method execution. However, since the target method needs to implement the same signature, it will have to return a temporary Future handle that just passes a value through: e.g. Spring's AsyncResult, EJB 3.1's AsyncResult, or CompletableFuture.completedFuture(Object).

涉及代理的事实也通过 @EnableAsync 注释元素中的两个指定代理详细信息这一事实变得显而易见:mode and proxyTargetClass.

问题示例

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

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

由于此任务发生在由 Spring 的 TaskExecutor 创建的单独线程中,调用代码将继续立即通过 GitHubLookupService.findUser 调用,即使它会至少需要 1 秒才能达到 return.

如果在调用代码中使用 findUser 调用的结果(例如使用 CompletableFuture.get()),它将从 Future 获得的值将相同 results 值在 GitHubLookupService 代码中传递给 CompletableFuture.completedFuture