Spring @Async Not Working

Viewed 94383

An @Async method in a @Service-annotated class is not being called asynchronously - it's blocking the thread.

I've got <task: annotation-driven /> in my config, and the call to the method is coming from outside of the class so the proxy should be being hit. When I step through the code, the proxy is indeed hit, but it doesn't seem to go anywhere near any classes related to running in a task executor.

I've put breakpoints in AsyncExecutionInterceptor and they never get hit. I've debugged into AsyncAnnotationBeanPostProcessor and can see advice getting applied.

The service is defined as an interface (with the method annotated @Async there for good measure) with the implementation's method annotated @Async too. Neither are marked @Transactional.

Any ideas what may have gone wrong?

-=UPDATE=-

Curiously, it works only when I have my task XML elements in my app-servlet.xml file, and not in my app-services.xml file, and if I do my component scanning over services from there too. Normally I have one XML file with only controllers in it (and restrict the component-scan accordingly), and another with services in it (again with a component-scan restricted such that it doesn't re-scan the controllers loaded in the other file).

app-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:jee="http://www.springframework.org/schema/jee" 
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:webflow="http://www.springframework.org/schema/webflow-config" 
xmlns:task="http://www.springframework.org/schema/task"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    http://www.springframework.org/schema/task
    http://www.springframework.org/schema/task/spring-task-3.0.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
    http://www.springframework.org/schema/jee 
    http://www.springframework.org/schema/jee/spring-jee-3.0.xsd"
>
<task:annotation-driven executor="executor" />
<task:executor id="executor" pool-size="7"/>

<!-- Enable controller annotations -->
<context:component-scan base-package="com.package.store">
    <!-- <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> -->
</context:component-scan>

<tx:annotation-driven/>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<mvc:annotation-driven conversion-service="conversionService" />

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/" />
    <property name="suffix" value=".jsp" />
</bean>

app-services.xml (doesn't work when specified here)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:context="http://www.springframework.org/schema/context" xmlns:task="http://www.springframework.org/schema/task"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/task
        http://www.springframework.org/schema/task/spring-task-3.0.xsd">

    <!-- Set up Spring to scan through various packages to find annotated classes -->
    <context:component-scan base-package="com.package.store">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

    <task:annotation-driven executor="han" />
    <task:executor id="han" pool-size="6"/>
    ...

Am I missing something glaringly obvious in my configuration, or is there some subtle interplay between config elements going on?

11 Answers

In my case the @Async method was defined in same class as the sync method that used it, and that apparently caused all jobs to hang on current thread.

Bad

@Component
@EnableAsync
public class TranslationGapiReader {
    @Async
    public CompletableFuture<GapiFile> readFile(String fileId) {
        try { Thread.sleep(2000); } catch (Exception exc) { throw new RuntimeException("ololo", exc); }
        return CompletableFuture.completedFuture(null);
    }

    public Stream<GapiFile> readFiles(Iterable<String> fileIds) {
        List<CompletableFuture<GapiFile>> futures = new ArrayList<>();
        for (String fileId: fileIds) {
            futures.add(readFile(fileId));
        }
        return Stream.empty();
    }
}

Good

@Component
@EnableAsync
public class AsyncGapiFileReader {
    @Async
    public CompletableFuture<TranslationGapiReader.GapiFile> readFile(String fileId) {
        try { Thread.sleep(2000); } catch (Exception exc) { throw new RuntimeException("ololo", exc); }
        return CompletableFuture.completedFuture(null);
    }
}
@Component
@EnableAsync
public class TranslationGapiReader {
    @Autowired
    AsyncGapiFileReader asyncGapiFileReader;

    public Stream<GapiFile> readFiles(Iterable<String> fileIds) {
        List<CompletableFuture<GapiFile>> futures = new ArrayList<>();
        for (String fileId: fileIds) {
            futures.add(asyncGapiFileReader.readFile(fileId));
        }
        return Stream.empty();
    }
}

I'm not Spring guru enough to understand why does it only work when the @Async method is in a different class, but that's what fixes the issue from my observations.

You need 3 lines of code for Async to work

  1. in applicationContext.xml
  2. At class level @EnableAsync
  3. @Async at method level

@Service @EnableAsync public myClass {

@Async public void myMethod(){

}

Try below: 1. In config create bean for ThreadPoolTaskExecutor

@Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        return new ThreadPoolTaskExecutor();
    }

2. In service method where @Async is used add

@Async("threadPoolTaskExecutor")
    public void asyncMethod(){
    //do something
    }

This should get @Async working.

Related