6

Can anybody tell my is there a way of using the Spring Framework's @Async annotation without blocking / waiting on the result? Here is some code to clarify my question:

@Service
public class AsyncServiceA {

    @Autowired
    private AsyncServiceB asyncServiceB;

    @Async
    public CompletableFuture<String> a() {
        ThreadUtil.silentSleep(1000);
        return asyncServiceB.b();
    }
}


@Service
public class AsyncServiceB {

    @Async
    public CompletableFuture<String> b() {
        ThreadUtil.silentSleep(1000);
        return CompletableFuture.completedFuture("Yeah, I come from another thread.");
    }
}

and the configuration:

@SpringBootApplication
@EnableAsync
public class Application implements AsyncConfigurer {

    private static final Log LOG = LogFactory.getLog(Application.class);

    private static final int THREAD_POOL_SIZE = 1;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
        return args -> {
            final AsyncServiceA bean = ctx.getBean(AsyncServiceA.class);
            bean.a().whenComplete(LOG::info);
        };
    }

    @Override
    @Bean(destroyMethod = "shutdown")
    public ThreadPoolTaskExecutor getAsyncExecutor() {
        final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(THREAD_POOL_SIZE);
        executor.setMaxPoolSize(THREAD_POOL_SIZE);
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        // omitted
    }
}

When I run the application the executor goes through calling AsyncServiceA.a() and leaves, but it still holds the thread from the pool waiting on the CompletableFuture.get() method. Since there is just a single thread in the pool the AsyncServiceB.b() cannot be executed. What I'm expecting is that thread to be returned to the pool after it executes the AsyncServiceA.a() and then be available to execute the AsyncServiceB.b().

Is there a way to do that?

Note 1: I've tried with ListenableFuture also but the result is the same.

Note 2: I've successfully did it manually (without the @Async) by giving the executor to each method like so:

AsyncServiceA

public CompletableFuture<String> manualA(Executor executor) {
    return CompletableFuture.runAsync(() -> {
        LOG.info("manualA() working...");
        ThreadUtil.silentSleep(1000);
    }, executor)
            .thenCompose(x -> asyncServiceB.manualB(executor));
}

AsyncServiceB

public CompletableFuture<String> manualB(Executor executor) {
    return CompletableFuture.runAsync(() -> {
        LOG.info("manualB() working...");
        ThreadUtil.silentSleep(1000);
    }, executor)
            .thenCompose(x -> CompletableFuture
                    .supplyAsync(() -> "Yeah, I come from another thread.", executor));
}

Here is the ThreadUtil if someone was wondering.

public class ThreadUtil {

    public static void silentSleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

Update: Added issue for non-blocking Async annotation https://jira.spring.io/browse/SPR-15401

7
  • Well what do you expect with a max pool size of 1... There is only a single thread to process the async methods so basically nothing is async with that. That thread is blocked and not returned to the pool until that has finished. Commented Mar 29, 2017 at 18:01
  • I've explained my expectations in the question above - operations on the CompletableFuture are non-blocking until you call get. I don't call get() anywhere so I expect the thread to be done with this CompletableFuture and get returned to the pool. Unfortunately the @Async annotation force call to the get() method and thus the thread is blocked - I want to escape from that. I know this is not asynchronous with thread pool with size 1, but in a non-blocking scenario it would work like a charm. Commented Mar 29, 2017 at 18:39
  • I'm not sure if I understand your last comment. I never reach the b() method call - the a(), which is async returns and blocks the thread before b() is called in another thread and there is no thread in the pool for b(). I'm confident b() is never expected because I've added aspect to log when method is entered and when it returns. I've also debugged it tens of times. Commented Mar 29, 2017 at 19:07
  • I removed the comment (before I could write another one :) ). Spring is calling get() so it works not the same as your example where you are composing stuff. So basically you are comparing apples and oranges and what you want cannot be achieved like this, you would still need to do the composing yourself. Also spring uses for both supplyAsync and not runAsync. (You might want to check the sources of the AsyncExecutionInterceptor to see what spring actually does. Commented Mar 29, 2017 at 19:12
  • :) Yes, I saw how the AsyncExecutionInterceptor works and I didn't like it (because I want to be in control when to call get()). I hoped that there is some workaround on this (with config or something), because it doesn't make sense to me why would it work like that (blocking execution). Well, maybe I'm asking for too much and should do the custom solution, then wait for the official release of Java 9 and Spring 5.0 with its reactive features. Commented Mar 29, 2017 at 19:29

2 Answers 2

4

The @Async support has been part of Spring since Spring 3.0 which was way before the existence of Java8 (or 7 for that matter). Although support has been added for CompletableFutures in later versions it still is to be used for simple async execution of a method call. (The initial implementation reflects/shows the call).

For composing callbacks and operate non blocking the async support was never designed or intended to.

For non blocking support you would want to wait for Spring 5 with its reactive/non-blocking core, next to that you can always submit a ticket for non-blocking support in the async support.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! FYI I added the issue - link in the updated description.
3

I've responded on the ticket https://jira.spring.io/browse/SPR-15401 but I'll respond here as well to qualify the response by M. Deinum.

@Async by virtue of how it works (decorating the method call via AOP) can only do one thing, which is to turn the entire method from sync to async. That means the method has to be sync, not a mix of sync and async.

So ServiceA which does some sleeping and then delegates to the async ServiceB would have to wrap the sleeping part in some @Async ServiceC and then compose on ServiceB and C. That way ServiceA becomes async and does not need to have the @Async annotation itself..

1 Comment

Please forgive my ignorance, but wouldn't this approach still tie up the thread from which ServiceA is called with an underlying get() call on each of the @Async service calls? If so wouldn't this be frowned upon when ServiceA is initiated from a web service?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.