Asynchronous Method Call
Posted on
Async calls with spring
What do you need async calls for?
Imagine you have a site comparing different prices of different online-shops.
It takes around 2 seconds to fetch the prices of each site. You start small,
compare the prices of three online shops, so it will take around 6 seconds for
each product, if you make them one by one. When you grow and check the prices
of 10 shops it will already take you 20 seconds.
That’s a long time. There’s no need to wait until the first shop has finished
to check the next one, as you do not need the result of ony of thos to get the
results of the others.
So instead of running them one by one, you can run them in parallel. Assuming
enough power an network-bandwidth it doesn’t matter how many shops you query,
when you query them in parallel the longes will define the time it takes for
the whole result.
A simple example
We have a little processor that just returns the thread-name after a while (2 seconds in this example).
public class AsyncProcessor {
public String longTimeRunningMethod() throws InterruptedException {
Thread.sleep(2 * 1000);
return Thread.currentThread().getName();
}
}
We run this using the following code:
public void run(ApplicationArguments arg0) throws Exception {
long started = System.nanoTime();
for (int i = 0; i < 4; i++) {
LOG.info("Received response: " +
processor.longTimeRunningMethod());
}
LOG.info("processing took "
+ Math.round((System.nanoTime() - started) / 1000000000)
+ " seconds");
}
This will run the longTimeRunningMethod
4 times. The results will be logged
out directly. We also take the time before running longTimeRunningMethod
4
times, and write out how long this takes.
When we run it, we see that 4 times the thread-name main
is written to the
console. The whole application took 8 seconds.
Implement parallel processing
Luckily spring does provide us with a simple solution to run methods
asynchronous. First spring needs to be told to use async, this is done by
annotating the application with @EnableAsync
, and the method is annotated
with @Async
. There’s only one thing left to do. The method cannot return a
String any more. We need to return a construct that represents the execution
and provides the result once the execution has finished. Spring defines the
Future
for that purpose. Future
is only a interface, so the method returns
a implementation, AsyncReuslt
.
@Async
public Future<String> longTimeRunningMethod() throws InterruptedException {
Thread.sleep(2 * 1000);
return new AsyncResult<String>(Thread.currentThread().getName());
}
If we run it like this nothing will have changed. This is because we try to get the result of each request before starting the next request. So we need to first start all the queries, then process the result.
We end up with this code to run the example:
@Async
public Future<String> longTimeRunningMethod() throws InterruptedException {
Thread.sleep(2 * 1000);
return new AsyncResult<String>(Thread.currentThread().getName());
}
public void run(ApplicationArguments arg0) throws Exception {
List<Future<String>> results = new ArrayList<Future<String>>();
long started = System.nanoTime();
for (int i = 0; i < 4; i++) {
results.add(processor.longTimeRunningMethod());
}
for (Future<String> result : results) {
LOG.info("Received response: " + result.get());
}
LOG.info("processing took "
+ Math.round((System.nanoTime() - started) / 1000000000)
+ " seconds");
}
If we now run the application, it finishes in 2 seconds.
Configure the Async processor
As you’ve seen, the response is also different. At the beginning all calls were
processed in the main
-thread. Now it’s processed in a thread for each call.
Actually with the default-configuration, there’s no limit and any call will
start a new Thread that is created. So if you want to use it in a real-live
application you should configure the Execution of asynchronous calls.
To make this, create a implementation of the AsyncConfigurer
. It needs you to
provide a Executor
and an AsyncUncaughtExceptionHandler
.
This is how it could look like.
@Configuration
public class AsyncConfiguration implements AsyncConfigurer {
private static final Logger LOG = LoggerFactory
.getLogger(AsyncConfiguration.class);
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("async-");
executor.setCorePoolSize(3);
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandler() {
@Override
public void handleUncaughtException(Throwable throwable,
Method method, Object... obj) {
LOG.error("Exception message: " + throwable.getMessage());
LOG.error("Method name: " + method.getName());
for (Object param : obj) {
LOG.error("Parameter value: " + param);
}
}
};
}
}
If you start again, it will take 4 seconds this time, because the Threadpool is limited to 3 Threads. so running 4 times, the fourth is called after the first three have finished.