The following example is taken from 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 my knowledge of async methods in computer science goes - It should return immediately. It should be non-blocking.
So lets say somewhere in Spring lets say my code findUser()
is called like so:
CompletableFuture<User> user = service.findUser("foo");
This would actually block. It would block a different thread on the Executor
service but it would block due to the Thread.sleep(1000L)
. Correct?
So how is this async?
I mean the whole point of CompletableFuture
is to get a reference to a computation that will be completed in the future. But here when I get back the completed future the computation is already over, ie. we are using CompletableFuture.completedFuture(results)
.
So what is the point of having a CompletableFuture
in this case? I mean if I am going to block and return only when my computation is over and I have the results, I might as well just return the result and not the CompletableFuture
.
How is this truly non-blocking/async?
The only non-blocking aspect I find here is offload to a different thread, nothing else.
Am I going wrong somewhere? What am I missing?
Thanks.
The problem lies in the way you create your Future
. The code you use is
CompletableFuture.completedFuture(results)
Quoting from the JavaDoc , this is only a wrapper between sync and async, where the computation was done synchronously:
Returns a new CompletableFuture that is already completed with the given value.
This is useful in certain situations where you only want to do asynchronous work for some inputs. Consider
(x) -> x==0 ? CompletableFuture.completedFuture(0) : CompletableFuture.supplyAsync(expensiveComputation)
I hope this makes the difference clear - if you want truly async computations, you need to use the supplyAsync
function:
Returns a new
CompletableFuture
that is asynchronously completed by a task running in theForkJoinPool.commonPool()
with the value obtained by calling the givenSupplier
.
The detail you're missing is that when @Async
is used (and correctly configured), a proxy bean will be used, wrapping your service bean. Any calls to the asynchronous methods through the proxy will use a Spring TaskExecutor
to asynchronously run the method.
Wrapping the method response in a synchronous Future
such as CompletableFuture.completedFuture
is necessary so the return type can be a Future
. However, the Future
you return is not the one returned by the proxy. Rather, the proxy returns a Future
provided by the TaskExecutor
, which will be processed asynchronously. The Future
you create through eg CompletableFuture.completedFuture
is unwrapped by the proxy, and its completion result is returned by the proxy's Future
.
I don't see all of the aforementioned proxying details explicitly stated in either the Spring reference documentation or in the @Async
or @EnableAsync
Javadocs. However, the details can be pieced together by reading between the lines of what is provided.
The @Async
Javadocs mentions the service proxy in passing, and explains why CompletableFuture.completedFuture
is used in the service method's implementation:
A
Future
handle returned from the proxy will be an actual asynchronousFuture
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 temporaryFuture
handle that just passes a value through: eg Spring'sAsyncResult
, EJB 3.1'sAsyncResult
, orCompletableFuture.completedFuture(Object)
.
The fact that proxying is involved is also made apparent by the fact that two of the @EnableAsync
annotation elements specify proxying details: mode
and proxyTargetClass
.
Finally, applying this to the question example will make it concrete. Code that calls the findUser
method on the GitHubLookupService
bean will actually be calling a method on a proxy class, rather than directly on the GitHubLookupService
instance. The proxy class's findUser
method submits a task to Spring's TaskExecutor
, and return a CompletableFuture
that will asynchronously be completed when the submitted task completes.
The submitted task will call the actual findUser
method in the non-proxied GitHubLookupService
. This will will perform the REST call, sleep 1 second, and return a completed CompletableFuture
with the REST results.
Since this task is happening in a separate thread created by Spring's TaskExecutor
, the calling code will continue to proceed past the GitHubLookupService.findUser
call immediately, even though it will take at least 1 second for it to return.
If the result of the findUser
call is used in the calling code (using eg CompletableFuture.get()
), the value it will get from that Future
will be the same results
value passed to CompletableFuture.completedFuture
in the GitHubLookupService
code.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.